Implement contract document download functionality and enhance clearance review status checks

- Added methods to assert clearance reviewable, finalizable, and uploadable statuses in the ContractClearanceService.
- Introduced a new endpoint in ContractsController for downloading contract PDFs.
- Implemented download functionality in the contracts service for both backoffice and portal applications.
- Updated UI components to include download buttons for contract PDFs in relevant pages.
- Enhanced contract request and view pages to support contract document downloads.
This commit is contained in:
marshal
2026-07-01 07:27:53 +03:00
parent 7654b18385
commit ccd5d6de31
24 changed files with 785 additions and 133 deletions

View File

@@ -0,0 +1,145 @@
import { useMemo, useState } from "react";
import { useQuery } from "@tanstack/react-query";
import {
Badge,
Button,
Center,
Group,
Loader,
Paper,
Stack,
Table,
Text,
Title,
} from "@mantine/core";
import { CalendarClock, Pencil } from "lucide-react";
import { PageContainer } from "@/components/page";
import Breadcrumbs from "@/components/ui/Breadcrumbs";
import { api } from "@/services/api";
import ManageDropdownOptionsDialog from "@/pages/dropdown_settings/ManageDropdownOptionsDialog";
const CONTRACT_VALIDITY_PERIODS_CODE = "contract_validity_periods";
/**
* Admin UI for contract validity options used when staff accepts a submitted
* contract (SUBMITTED → PENDING_APPROVAL). Backed by dropdown_settings.
*/
export default function ContractValidityPeriodsPage() {
const [editOpen, setEditOpen] = useState(false);
const { data: setting, isLoading, isError } = useQuery(
api.dropdownSettings.getByCode.queryOptions({
input: { code: CONTRACT_VALIDITY_PERIODS_CODE },
}),
);
const options = useMemo(
() =>
[...(setting?.children ?? [])].sort(
(a, b) => (a.order ?? 0) - (b.order ?? 0),
),
[setting],
);
return (
<PageContainer>
<Breadcrumbs
items={[
{ label: "Configuration", href: "/dashboard/configuration" },
{ label: "Contract validity periods" },
]}
/>
<Stack gap="lg" mt="md">
<Group justify="space-between" align="flex-start" wrap="wrap">
<Stack gap={4}>
<Title order={2}>Contract validity periods</Title>
<Text size="sm" c="dimmed" maw={560}>
Options shown when line staff accepts a submitted contract. Each
value is the number of days the contract stays valid from the
accept date.
</Text>
</Stack>
{setting && (
<Button
leftSection={<Pencil size={16} />}
color="edr-green"
onClick={() => setEditOpen(true)}
>
Edit options
</Button>
)}
</Group>
<Paper withBorder radius="lg" p="lg">
{isLoading ? (
<Center py="xl">
<Loader color="edr-green" />
</Center>
) : isError || !setting ? (
<Text c="dimmed">
Could not load contract validity settings. Ensure{" "}
<Text span ff="monospace" size="sm">
{CONTRACT_VALIDITY_PERIODS_CODE}
</Text>{" "}
is seeded in dropdown settings.
</Text>
) : options.length === 0 ? (
<Stack align="center" gap="md" py="xl">
<CalendarClock size={32} color="var(--mantine-color-gray-5)" />
<Text c="dimmed">No validity periods configured yet.</Text>
<Button
variant="light"
color="edr-green"
onClick={() => setEditOpen(true)}
>
Add options
</Button>
</Stack>
) : (
<Table striped highlightOnHover>
<Table.Thead>
<Table.Tr>
<Table.Th>Label</Table.Th>
<Table.Th>Days (value)</Table.Th>
<Table.Th>Order</Table.Th>
<Table.Th>Status</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{options.map((opt) => (
<Table.Tr key={opt.id}>
<Table.Td>{opt.label}</Table.Td>
<Table.Td>
<Text ff="monospace" size="sm">
{opt.value}
</Text>
</Table.Td>
<Table.Td>{opt.order ?? "—"}</Table.Td>
<Table.Td>
<Badge
color={opt.disabled ? "gray" : "edr-green"}
variant="light"
>
{opt.disabled ? "Disabled" : "Active"}
</Badge>
</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
</Table>
)}
</Paper>
</Stack>
{setting ? (
<ManageDropdownOptionsDialog
setting={setting}
open={editOpen}
onOpenChange={setEditOpen}
/>
) : null}
</PageContainer>
);
}