mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 01:48:12 +00:00
feat(freight): track onboarding-phase edit history for companies
Ticket #238 — pre-approval edits and document uploads write straight to the live company row with no approval gate and, until now, no trace. Adds an append-only company_revisions log (diffed field changes, document uploads) recorded from updateProfile and uploadCompanyDocuments, exposed via GET /companies/:id/revisions and shown as "Version history" on the backoffice customer detail page.
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
import { Badge, Box, Card, Group, Stack, Text } from "@mantine/core";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { History } from "lucide-react";
|
||||
|
||||
import { api } from "@/services/api";
|
||||
import { formatDate } from "./format";
|
||||
|
||||
/**
|
||||
* Onboarding-phase edit history: what changed on the company record before it
|
||||
* reached Active, the write path that has no approval gate (unlike edits made
|
||||
* after approval, which go through {@link ChangeRequestReview} instead).
|
||||
*/
|
||||
export function CompanyRevisionHistory({ companyId }: { companyId: string }) {
|
||||
const query = useQuery(
|
||||
api.customers.revisions.queryOptions({ input: { id: companyId } }),
|
||||
);
|
||||
const revisions = query.data ?? [];
|
||||
if (revisions.length === 0) return null;
|
||||
|
||||
return (
|
||||
<Card withBorder>
|
||||
<Stack gap="sm">
|
||||
<Group gap="xs">
|
||||
<History size={16} />
|
||||
<Text fw={600} c="edr-text">
|
||||
Version history
|
||||
</Text>
|
||||
</Group>
|
||||
{revisions.map((rev) => (
|
||||
<Box key={rev.id}>
|
||||
<Group gap="sm" wrap="nowrap" align="flex-start">
|
||||
<Badge color="gray" variant="light" radius="md" tt="none">
|
||||
{formatDate(rev.createdAt)}
|
||||
</Badge>
|
||||
<Text size="sm" c="edr-text" tt="capitalize">
|
||||
{rev.summary}
|
||||
</Text>
|
||||
</Group>
|
||||
{rev.changes.length > 0 && (
|
||||
<Stack gap={2} ml={4} mt={4}>
|
||||
{rev.changes.map((c, i) => (
|
||||
<Text key={i} size="xs" c="dimmed">
|
||||
{c.label}: {c.from ?? "—"} → {c.to ?? "—"}
|
||||
</Text>
|
||||
))}
|
||||
</Stack>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
</Stack>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -13,6 +13,7 @@ export {
|
||||
ChangeRequestReview,
|
||||
ChangeRequestPendingBadge,
|
||||
} from "./ChangeRequestReview";
|
||||
export { CompanyRevisionHistory } from "./CompanyRevisionHistory";
|
||||
export {
|
||||
RequestDocumentChangeModal,
|
||||
type RequestDocumentChangeModalProps,
|
||||
|
||||
@@ -42,6 +42,8 @@ export const QUERY_KEYS = {
|
||||
["customers", "detail", id, "reset-target"] as const,
|
||||
changeRequests: (id: string) =>
|
||||
["customers", "detail", id, "change-requests"] as const,
|
||||
revisions: (id: string) =>
|
||||
["customers", "detail", id, "revisions"] as const,
|
||||
},
|
||||
|
||||
INVOICES: {
|
||||
|
||||
@@ -78,6 +78,7 @@ export const URL_CONSTANTS = {
|
||||
`/companies/company-profiles/${profileId}/status`,
|
||||
CHANGE_REQUESTS: (companyId: string) =>
|
||||
`/companies/${companyId}/change-requests`,
|
||||
REVISIONS: (companyId: string) => `/companies/${companyId}/revisions`,
|
||||
CHANGE_REQUEST_APPROVE: (id: string) =>
|
||||
`/companies/change-requests/${id}/approve`,
|
||||
CHANGE_REQUEST_REJECT: (id: string) =>
|
||||
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
BookingStatusBadge,
|
||||
ChangeRequestPendingBadge,
|
||||
ChangeRequestReview,
|
||||
CompanyRevisionHistory,
|
||||
CompanyStatusBadge,
|
||||
CompanyTypeBadge,
|
||||
InvoiceStatusBadge,
|
||||
@@ -710,6 +711,7 @@ export default function CustomerDetailPage() {
|
||||
)}
|
||||
|
||||
<ChangeRequestReview company={company} />
|
||||
<CompanyRevisionHistory companyId={company.id} />
|
||||
|
||||
<KpiStrip
|
||||
items={[
|
||||
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
CompanyChangeRequest,
|
||||
CompanyListFilter,
|
||||
CompanyProfile,
|
||||
CompanyRevision,
|
||||
CompanyStats,
|
||||
CustomerBooking,
|
||||
CustomerDocument,
|
||||
@@ -2778,6 +2779,13 @@ export const api = {
|
||||
({ id }) => QUERY_KEYS.CUSTOMERS.changeRequests(id),
|
||||
),
|
||||
|
||||
revisions: endpoint<{ id: string }, CompanyRevision[]>(
|
||||
"customers",
|
||||
"revisions",
|
||||
({ id }) => customersService.revisions(id),
|
||||
({ id }) => QUERY_KEYS.CUSTOMERS.revisions(id),
|
||||
),
|
||||
|
||||
approveChangeRequest: endpoint<{ id: string }, CompanyChangeRequest>(
|
||||
"customers",
|
||||
"approveChangeRequest",
|
||||
|
||||
@@ -5,6 +5,7 @@ import type {
|
||||
CompanyChangeRequest,
|
||||
CompanyListFilter,
|
||||
CompanyProfile,
|
||||
CompanyRevision,
|
||||
CompanyStats,
|
||||
CustomerBooking,
|
||||
CustomerDocument,
|
||||
@@ -146,6 +147,13 @@ export const customersService = {
|
||||
.then((r) => r.data);
|
||||
},
|
||||
|
||||
/** Onboarding-phase edit history for a company (version history), newest first. */
|
||||
revisions(companyId: string): Promise<CompanyRevision[]> {
|
||||
return apiClient
|
||||
.get<CompanyRevision[]>(URL_CONSTANTS.COMPANIES.REVISIONS(companyId))
|
||||
.then((r) => r.data);
|
||||
},
|
||||
|
||||
/** Approve a pending change request — applies the proposed changes. */
|
||||
approveChangeRequest(id: string): Promise<CompanyChangeRequest> {
|
||||
return apiClient
|
||||
|
||||
Reference in New Issue
Block a user