shoc-frontend-new/src/app/(protected)/vendors/_components/vendor-roster-page.tsx
arthur.bassi a05e768115 fix(work-orders): address Bugbot findings on slide-over and vendor roster
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-10 18:35:28 -03:00

177 lines
5.6 KiB
TypeScript

import type { ReactNode } from "react";
import { useNavigate } from "react-router";
import { Alert, Box, Button, CircularProgress, Paper, Stack, Typography } from "@mui/material";
import { VendorDeactivationDialog } from "./vendor-deactivation-dialog";
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 { useVendorDeactivation } from "./use-vendor-deactivation";
import { useVendorRosterForm } from "./use-vendor-roster-form";
import type { RosterTechnicianValues } from "@/domain/vendors/schemas/vendor-roster-schema";
import type { VendorCompanyRoster, VendorListItem } from "@/domain/vendors/types/vendor";
interface VendorRosterPageProps {
vendorId?: string;
companyId?: string;
}
function toDeactivationListItem(
roster: VendorCompanyRoster,
technician: RosterTechnicianValues,
): VendorListItem {
return {
id: technician.id ?? null,
companyId: roster.companyId,
companyName: roster.name,
contactName: technician.contactName,
email: technician.email,
phone: technician.phone,
companyPhone: roster.companyPhone,
googleMapsUrl: roster.googleMapsUrl,
notes: roster.notes,
totalJobs: 0,
city: roster.city,
state: roster.state,
tradeSpecialties: technician.tradeSpecialties,
isActive: technician.isActive,
preferredContact: technician.preferredContact ?? "Phone",
};
}
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 deactivation = useVendorDeactivation({
onSuccess: () => navigate("/vendors"),
});
const form = useVendorRosterForm({
mode: isEdit ? "update" : "create",
vendorId,
companyId,
onSuccess: () => navigate("/vendors"),
});
const handleRequestDeactivate = (technician: RosterTechnicianValues) => {
if (!form.roster || technician.id == null) return;
deactivation.open(toDeactivationListItem(form.roster, technician));
};
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}
onRequestDeactivate={isEdit ? handleRequestDeactivate : undefined}
{...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>
<VendorDeactivationDialog
target={deactivation.target}
isLoading={deactivation.isLoading}
isSaving={deactivation.isSaving}
error={deactivation.error}
impact={deactivation.impact}
impactError={deactivation.impactError}
onClose={deactivation.close}
onConfirm={deactivation.confirm}
/>
</PageShell>
);
}