mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 16:28:12 +00:00
Merge pull request #983 from Tria-plc/freight_feature/usermanagement
Freight feature/usermanagement
This commit is contained in:
@@ -5,7 +5,7 @@ import { Button, Group, Modal, Stack, Text, Textarea } from "@mantine/core";
|
||||
import {
|
||||
Check,
|
||||
Eye,
|
||||
FilePen,
|
||||
// FilePen, // ponytail: back with the "Edit contract articles" button
|
||||
FileSignature,
|
||||
MessageSquareWarning,
|
||||
PauseCircle,
|
||||
@@ -286,9 +286,9 @@ export function ContractActionsToolbar({
|
||||
<>
|
||||
<Text size="xs" c="dimmed">
|
||||
{canEditDocument
|
||||
? "It is your turn to approve. You can edit the articles before approving — the PDF is generated automatically once the last approver approves."
|
||||
? "It is your turn to approve — the PDF is generated automatically once the last approver approves."
|
||||
: draft?.nextApproverRole
|
||||
? `Awaiting ${draft.nextApproverRole}. Only the current approver can edit the document.`
|
||||
? `Awaiting ${draft.nextApproverRole}.`
|
||||
: "Awaiting approval."}
|
||||
</Text>
|
||||
<Button
|
||||
@@ -300,6 +300,9 @@ export function ContractActionsToolbar({
|
||||
>
|
||||
Preview document
|
||||
</Button>
|
||||
{/* Article editing is hidden for now (frontend only) — the approval
|
||||
chain approves the document as accepted. Uncomment to restore.
|
||||
|
||||
{canEditDocument && (
|
||||
<Button
|
||||
fullWidth
|
||||
@@ -314,6 +317,8 @@ export function ContractActionsToolbar({
|
||||
Edit contract articles
|
||||
</Button>
|
||||
)}
|
||||
|
||||
*/}
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -95,12 +95,16 @@ export function RequestCustomerCard({ contract }: { contract?: ReqContract | nul
|
||||
);
|
||||
}
|
||||
|
||||
// Validity is accepted to the minute, so the expiry reads with its time — a
|
||||
// contract that lapses at 09:00 looks identical to one lapsing at 23:59 without it.
|
||||
const fmtDate = (iso?: string | null) =>
|
||||
iso
|
||||
? new Intl.DateTimeFormat("en-GB", {
|
||||
day: "2-digit",
|
||||
month: "short",
|
||||
year: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
}).format(new Date(iso))
|
||||
: "—";
|
||||
|
||||
|
||||
@@ -110,6 +110,21 @@ function formatDate(value: string | null | undefined): string {
|
||||
});
|
||||
}
|
||||
|
||||
/** Same, plus the clock — for values the staff pick to the minute. */
|
||||
function formatDateTime(value: string | null | undefined): string {
|
||||
if (!value) return "—";
|
||||
const d = new Date(value);
|
||||
return Number.isNaN(d.getTime())
|
||||
? "—"
|
||||
: d.toLocaleString(undefined, {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
}
|
||||
|
||||
export default function ContractRequestDetailPage() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const navigate = useNavigate();
|
||||
@@ -370,7 +385,8 @@ export default function ContractRequestDetailPage() {
|
||||
{contract.contractValidUntil ? (
|
||||
<MetaItem
|
||||
icon={CalendarClock}
|
||||
text={`Valid until ${formatDate(contract.contractValidUntil)}`}
|
||||
// Validity is accepted to the minute — show the time.
|
||||
text={`Valid until ${formatDateTime(contract.contractValidUntil)}`}
|
||||
/>
|
||||
) : null}
|
||||
</Group>
|
||||
|
||||
Reference in New Issue
Block a user