shoc-frontend-new/src/app/(protected)/vendors/_components/vendor-roster-page.tsx
Alexandre Brandizzi ca7829036f
Some checks failed
CI / ci (push) Has been cancelled
CI / governance (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
feat(vendors): add company roster workflows (SH-198) (#64)
* feat(vendors): support company roster workflows

* fix(vendors): address roster review feedback

* test(vendors): preserve draft on roster load failure

* fix(vendors): preserve current roster selection
2026-08-04 09:53:48 -03:00

130 lines
4 KiB
TypeScript

import type { ReactNode } from "react";
import { useNavigate } from "react-router";
import { Alert, Box, Button, CircularProgress, Paper, Stack, Typography } from "@mui/material";
import { VendorPortalTokenPanel } from "./vendor-portal-token-panel";
import { VendorRosterConflictAlert } from "./vendor-roster-conflict-alert";
import { VendorRosterFormFields } from "./vendor-roster-form-fields";
import { VendorRosterLoadErrorAlert } from "./vendor-roster-load-error";
import { useVendorRosterForm } from "./use-vendor-roster-form";
interface VendorRosterPageProps {
vendorId?: string;
companyId?: string;
}
function PageShell({
title,
subtitle,
children,
}: {
title: string;
subtitle: string;
children: ReactNode;
}) {
return (
<Box className="mx-auto max-w-4xl p-4">
<Paper variant="outlined" className="p-6">
<Stack spacing={4}>
<Box>
<Typography variant="h5" component="h1">
{title}
</Typography>
<Typography variant="body2" sx={{ color: "text.secondary", mt: 0.5 }}>
{subtitle}
</Typography>
</Box>
{children}
</Stack>
</Paper>
</Box>
);
}
export default function VendorRosterPage({ vendorId, companyId }: VendorRosterPageProps) {
const navigate = useNavigate();
const isEdit = vendorId !== undefined || companyId !== undefined;
const form = useVendorRosterForm({
mode: isEdit ? "update" : "create",
vendorId,
companyId,
onSuccess: () => navigate("/vendors"),
});
if (isEdit && form.isLoading) {
return (
<Box className="flex justify-center p-10">
<CircularProgress />
</Box>
);
}
if (isEdit && form.isError) {
return (
<PageShell title="Vendor company" subtitle="Edit the company and its technicians.">
<Alert severity="error">Failed to load vendor company. Try again.</Alert>
<Stack direction="row" sx={{ justifyContent: "flex-end" }}>
<Button variant="outlined" onClick={() => navigate("/vendors")}>
Back to vendors
</Button>
</Stack>
</PageShell>
);
}
if (isEdit && !form.roster) {
return null;
}
const title = isEdit ? "Edit Vendor Company" : "Add Vendor Company";
const subtitle = isEdit
? "Update company details and technicians in one save."
: "Register a company and add zero, one, or multiple technicians.";
const companySelectionProps = isEdit
? {}
: {
companies: form.companies,
selectedCompanyId: form.selectedCompanyId,
onSelectCompany: form.selectCompany,
onClearSelectedCompany: form.clearSelectedCompany,
};
return (
<PageShell title={title} subtitle={subtitle}>
<Box component="form" onSubmit={form.handleSubmit(form.submit)}>
<Stack spacing={3}>
{form.loadErrorMessage !== null && (
<VendorRosterLoadErrorAlert message={form.loadErrorMessage} onRetry={form.retryLoad} />
)}
{form.conflict !== null && (
<VendorRosterConflictAlert conflict={form.conflict} onReload={form.reload} />
)}
<VendorRosterFormFields
control={form.control}
errors={form.errors}
tradeOptions={form.trades}
{...companySelectionProps}
/>
{isEdit && vendorId !== undefined && vendorId !== "" && (
<VendorPortalTokenPanel vendorId={vendorId} />
)}
<Stack direction="row" spacing={2} sx={{ justifyContent: "flex-end" }}>
<Button
variant="outlined"
onClick={() => navigate("/vendors")}
disabled={form.isSaving}
>
Cancel
</Button>
<Button type="submit" variant="contained" disabled={form.isSaving || !form.isValid}>
{form.isSaving ? "Saving..." : isEdit ? "Save" : "Create"}
</Button>
</Stack>
</Stack>
</Box>
</PageShell>
);
}