update contract document handling and improve clarity in UI messages

This commit is contained in:
Marshal
2026-07-28 05:51:24 +00:00
parent 8477de41a9
commit f40a079420
13 changed files with 236 additions and 81 deletions

View File

@@ -14,18 +14,18 @@ import {
Text,
Textarea,
TextInput,
Tooltip,
// Tooltip, // ponytail: back with the article editor block
} from "@mantine/core";
import {
ArrowDown,
ArrowUp,
// ArrowDown, // ponytail: back with the article editor block
// ArrowUp,
FileText,
Info,
Lock,
Plus,
Trash2,
} from "lucide-react";
import { DateInput } from "@mantine/dates";
import { DateTimePicker } from "@mantine/dates";
import type { Freight } from "@edr/types";
import { contractsService } from "@/services/contracts.service";
@@ -44,6 +44,23 @@ function startOfToday(): Date {
return d;
}
/** Local `YYYY-MM-DD` — the shape Mantine hands day cells. */
function localDay(date: Date): string {
const pad = (n: number) => String(n).padStart(2, "0");
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
}
/**
* Print today in bold inside the calendar. `highlightToday` only rings the cell,
* which staff read as "disabled" on a picker whose minimum IS today — the weight
* makes it obvious the day is pickable.
*/
function boldToday(date: string) {
return date === localDay(new Date())
? { style: { fontWeight: 800 } }
: {};
}
interface EditableArticle {
id: string;
title: string;
@@ -125,11 +142,14 @@ export function ContractDocumentEditorModal({
);
}, [opened, draft]);
// Accept mode opens on today — a contract never starts in the past, and the
// pickers below refuse earlier days.
// Accept mode opens on NOW — a contract never starts in the past, and the
// pickers below refuse earlier days. Seconds are dropped so the value matches
// what the HH:mm picker shows.
useEffect(() => {
if (!opened || mode !== "accept") return;
setValidityStart(startOfToday());
const now = new Date();
now.setSeconds(0, 0);
setValidityStart(now);
setValidityEnd(null);
}, [opened, mode]);
@@ -144,29 +164,30 @@ export function ContractDocumentEditorModal({
// decides per-caller — the client cannot derive this from the contract alone.
const locked = mode === "edit" && !draft?.editableByMe;
const moveArticle = (index: number, delta: number) => {
setArticles((prev) => {
const next = [...prev];
const target = index + delta;
if (target < 0 || target >= next.length) return prev;
[next[index], next[target]] = [next[target], next[index]];
return next;
});
};
// Article edit handlers — parked with the editor block below.
// const moveArticle = (index: number, delta: number) => {
// setArticles((prev) => {
// const next = [...prev];
// const target = index + delta;
// if (target < 0 || target >= next.length) return prev;
// [next[index], next[target]] = [next[target], next[index]];
// return next;
// });
// };
const updateArticle = (id: string, patch: Partial<EditableArticle>) =>
setArticles((prev) =>
prev.map((a) => (a.id === id ? { ...a, ...patch } : a)),
);
// const updateArticle = (id: string, patch: Partial<EditableArticle>) =>
// setArticles((prev) =>
// prev.map((a) => (a.id === id ? { ...a, ...patch } : a)),
// );
const removeArticle = (id: string) =>
setArticles((prev) => prev.filter((a) => a.id !== id));
// const removeArticle = (id: string) =>
// setArticles((prev) => prev.filter((a) => a.id !== id));
const addArticle = () =>
setArticles((prev) => [
...prev,
{ id: newArticleId(), title: "", body: "" },
]);
// const addArticle = () =>
// setArticles((prev) => [
// ...prev,
// { id: newArticleId(), title: "", body: "" },
// ]);
const buildSnapshot = (): Freight.IContractDocumentSnapshot => ({
code: draft?.code ?? null,
@@ -253,7 +274,7 @@ export function ContractDocumentEditorModal({
? draft?.nextApproverRole
? `Only the current approver (${draft.nextApproverRole}) can edit this document right now.`
: "This document can no longer be edited — the contract has advanced beyond approval."
: "Edits apply to THIS contract only. The six shared templates are never changed."}
: "Articles come from this contract's template. Set the validity dates, then accept."}
</Alert>
<TextInput
@@ -320,6 +341,11 @@ export function ContractDocumentEditorModal({
)}
</Box>
{/* Article editing is hidden for now (frontend only) — staff accept the
contract on the template's articles as-is. The articles themselves
still ride along in buildSnapshot(), so the generated document is
unchanged. Uncomment this block to bring the editor back.
<Divider label="Articles" labelPosition="left" />
<Stack gap="md">
@@ -379,7 +405,7 @@ export function ContractDocumentEditorModal({
}
/>
<Textarea
placeholder="Article body — each line becomes a numbered clause. Use '- ' for bullets. Placeholders like {{client.companyName}} are supported."
placeholder="Article body — each line becomes a numbered clause."
autosize
minRows={3}
styles={{ input: { fontFamily: "var(--mantine-font-family-monospace)" } }}
@@ -404,6 +430,8 @@ export function ContractDocumentEditorModal({
</Button>
</Stack>
*/}
<Divider />
{mode === "accept" && (
@@ -429,21 +457,29 @@ export function ContractDocumentEditorModal({
</Text>
)} */}
<Group grow align="flex-start">
<DateInput
label="Start date"
<DateTimePicker
label="Start date & time"
placeholder="Contract validity start"
value={validityStart}
onChange={(v) => setValidityStart(v ? new Date(v) : null)}
// Today is the earliest start — and it is ringed in the
// calendar so it reads as selectable rather than blocked.
minDate={startOfToday()}
maxDate={validityEnd ?? undefined}
highlightToday
getDayProps={boldToday}
valueFormat="DD MMM YYYY HH:mm"
clearable
/>
<DateInput
label="End date"
<DateTimePicker
label="End date & time"
placeholder="Contract validity end"
value={validityEnd}
onChange={(v) => setValidityEnd(v ? new Date(v) : null)}
minDate={validityStart ?? startOfToday()}
highlightToday
getDayProps={boldToday}
valueFormat="DD MMM YYYY HH:mm"
clearable
/>
</Group>