mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-01 08:33:11 +00:00
fix(services): satisfy registry maintainability gate
This commit is contained in:
parent
9c360fc95b
commit
2fb5d4a965
2 changed files with 203 additions and 165 deletions
|
|
@ -179,125 +179,15 @@ export function ServiceEditorDrawer({
|
|||
<Box className="flex-1 overflow-y-auto p-5">
|
||||
<Stack spacing={2.5}>
|
||||
{Boolean(formError) && <AlertMessage message={formError} />}
|
||||
<Typography variant="subtitle2" className="font-semibold">
|
||||
Service Information
|
||||
</Typography>
|
||||
<TextField
|
||||
label="Service Name"
|
||||
value={form.name}
|
||||
required
|
||||
fullWidth
|
||||
onChange={(event) => onUpdate("name", event.target.value)}
|
||||
placeholder="e.g., HVAC Inspection"
|
||||
<ServiceEditorFields
|
||||
mode={mode}
|
||||
form={form}
|
||||
trades={trades}
|
||||
templates={templates}
|
||||
canChangeActiveState={canChangeActiveState}
|
||||
onUpdate={onUpdate}
|
||||
onToggleType={onToggleType}
|
||||
/>
|
||||
<FormControl fullWidth required>
|
||||
<InputLabel id="service-trade-label">Trade</InputLabel>
|
||||
<Select
|
||||
labelId="service-trade-label"
|
||||
label="Trade"
|
||||
value={form.trade}
|
||||
onChange={(event) => onUpdate("trade", event.target.value)}
|
||||
>
|
||||
{trades.map((trade) => (
|
||||
<MenuItem key={String(trade.id)} value={trade.value}>
|
||||
{trade.value}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl fullWidth required>
|
||||
<InputLabel id="service-icon-label">Icon</InputLabel>
|
||||
<Select
|
||||
labelId="service-icon-label"
|
||||
label="Icon"
|
||||
value={form.iconKey}
|
||||
onChange={(event) => onUpdate("iconKey", event.target.value)}
|
||||
renderValue={(value) => {
|
||||
const icon = ICONS.find((item) => item.key === value);
|
||||
return icon ? (
|
||||
<Stack direction="row" spacing={1} sx={{ alignItems: "center" }}>
|
||||
<icon.Icon size={18} />
|
||||
<span>{icon.label}</span>
|
||||
</Stack>
|
||||
) : (
|
||||
"Pick an icon"
|
||||
);
|
||||
}}
|
||||
>
|
||||
{ICONS.map(({ key, label, Icon }) => (
|
||||
<MenuItem key={key} value={key}>
|
||||
<Stack direction="row" spacing={1} sx={{ alignItems: "center" }}>
|
||||
<Icon size={18} />
|
||||
<span>{label}</span>
|
||||
</Stack>
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
<FormHelperText>Pre-selected from the Trade; you can change it.</FormHelperText>
|
||||
</FormControl>
|
||||
<Typography variant="subtitle2" className="font-semibold">
|
||||
Supported Work Order Types
|
||||
</Typography>
|
||||
<Stack direction="row" sx={{ flexWrap: "wrap", gap: 1 }}>
|
||||
{SERVICE_WORK_ORDER_TYPES.map((type) => (
|
||||
<FormControlLabel
|
||||
key={type}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={form.supportedWorkOrderTypes.includes(type)}
|
||||
onChange={() => onToggleType(type)}
|
||||
/>
|
||||
}
|
||||
label={type}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
{mode === "edit" && canChangeActiveState && (
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={form.isActive ?? true}
|
||||
onChange={(event) => onUpdate("isActive", event.target.checked)}
|
||||
/>
|
||||
}
|
||||
label="Active"
|
||||
/>
|
||||
)}
|
||||
<Divider />
|
||||
<Typography variant="subtitle2" className="font-semibold">
|
||||
Completion Document
|
||||
</Typography>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={form.requiresCompletionDocument}
|
||||
onChange={(event) => {
|
||||
onUpdate("requiresCompletionDocument", event.target.checked);
|
||||
if (!event.target.checked) onUpdate("completionDocTemplateId", null);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="Requires completion document"
|
||||
/>
|
||||
{form.requiresCompletionDocument && (
|
||||
<Autocomplete
|
||||
options={templates}
|
||||
value={
|
||||
templates.find((template) => template.id === form.completionDocTemplateId) ?? null
|
||||
}
|
||||
onChange={(_, template) =>
|
||||
onUpdate("completionDocTemplateId", template?.id ?? null)
|
||||
}
|
||||
getOptionLabel={(template) => template.name}
|
||||
isOptionEqualToValue={(option, value) => String(option.id) === String(value.id)}
|
||||
renderInput={(params) => (
|
||||
<TextField {...params} label="Linked Template" placeholder="Select a template" />
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
Manage the document's own content on the Completion Document Templates page.
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Divider />
|
||||
|
|
@ -312,6 +202,141 @@ export function ServiceEditorDrawer({
|
|||
);
|
||||
}
|
||||
|
||||
type EditorFieldsProps = Pick<
|
||||
EditorProps,
|
||||
"mode" | "form" | "trades" | "templates" | "canChangeActiveState" | "onUpdate" | "onToggleType"
|
||||
>;
|
||||
|
||||
function ServiceEditorFields({
|
||||
mode,
|
||||
form,
|
||||
trades,
|
||||
templates,
|
||||
canChangeActiveState,
|
||||
onUpdate,
|
||||
onToggleType,
|
||||
}: EditorFieldsProps) {
|
||||
return (
|
||||
<>
|
||||
<Typography variant="subtitle2" className="font-semibold">
|
||||
Service Information
|
||||
</Typography>
|
||||
<TextField
|
||||
label="Service Name"
|
||||
value={form.name}
|
||||
required
|
||||
fullWidth
|
||||
onChange={(event) => onUpdate("name", event.target.value)}
|
||||
placeholder="e.g., HVAC Inspection"
|
||||
/>
|
||||
<FormControl fullWidth required>
|
||||
<InputLabel id="service-trade-label">Trade</InputLabel>
|
||||
<Select
|
||||
labelId="service-trade-label"
|
||||
label="Trade"
|
||||
value={form.trade}
|
||||
onChange={(event) => onUpdate("trade", event.target.value)}
|
||||
>
|
||||
{trades.map((trade) => (
|
||||
<MenuItem key={String(trade.id)} value={trade.value}>
|
||||
{trade.value}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl fullWidth required>
|
||||
<InputLabel id="service-icon-label">Icon</InputLabel>
|
||||
<Select
|
||||
labelId="service-icon-label"
|
||||
label="Icon"
|
||||
value={form.iconKey}
|
||||
onChange={(event) => onUpdate("iconKey", event.target.value)}
|
||||
renderValue={(value) => {
|
||||
const icon = ICONS.find((item) => item.key === value);
|
||||
return icon ? (
|
||||
<Stack direction="row" spacing={1} sx={{ alignItems: "center" }}>
|
||||
<icon.Icon size={18} />
|
||||
<span>{icon.label}</span>
|
||||
</Stack>
|
||||
) : (
|
||||
"Pick an icon"
|
||||
);
|
||||
}}
|
||||
>
|
||||
{ICONS.map(({ key, label, Icon }) => (
|
||||
<MenuItem key={key} value={key}>
|
||||
<Stack direction="row" spacing={1} sx={{ alignItems: "center" }}>
|
||||
<Icon size={18} />
|
||||
<span>{label}</span>
|
||||
</Stack>
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
<FormHelperText>Pre-selected from the Trade; you can change it.</FormHelperText>
|
||||
</FormControl>
|
||||
<Typography variant="subtitle2" className="font-semibold">
|
||||
Supported Work Order Types
|
||||
</Typography>
|
||||
<Stack direction="row" sx={{ flexWrap: "wrap", gap: 1 }}>
|
||||
{SERVICE_WORK_ORDER_TYPES.map((type) => (
|
||||
<FormControlLabel
|
||||
key={type}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={form.supportedWorkOrderTypes.includes(type)}
|
||||
onChange={() => onToggleType(type)}
|
||||
/>
|
||||
}
|
||||
label={type}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
{mode === "edit" && canChangeActiveState && (
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={form.isActive ?? true}
|
||||
onChange={(event) => onUpdate("isActive", event.target.checked)}
|
||||
/>
|
||||
}
|
||||
label="Active"
|
||||
/>
|
||||
)}
|
||||
<Divider />
|
||||
<Typography variant="subtitle2" className="font-semibold">
|
||||
Completion Document
|
||||
</Typography>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={form.requiresCompletionDocument}
|
||||
onChange={(event) => {
|
||||
onUpdate("requiresCompletionDocument", event.target.checked);
|
||||
if (!event.target.checked) onUpdate("completionDocTemplateId", null);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="Requires completion document"
|
||||
/>
|
||||
{form.requiresCompletionDocument && (
|
||||
<Autocomplete
|
||||
options={templates}
|
||||
value={templates.find((template) => template.id === form.completionDocTemplateId) ?? null}
|
||||
onChange={(_, template) => onUpdate("completionDocTemplateId", template?.id ?? null)}
|
||||
getOptionLabel={(template) => template.name}
|
||||
isOptionEqualToValue={(option, value) => String(option.id) === String(value.id)}
|
||||
renderInput={(params) => (
|
||||
<TextField {...params} label="Linked Template" placeholder="Select a template" />
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
Manage the document's own content on the Completion Document Templates page.
|
||||
</Typography>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function AlertMessage({ message }: { message: string }) {
|
||||
return (
|
||||
<Box className="rounded border border-red-200 bg-red-50 p-2 text-sm text-red-700">
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import {
|
|||
useServicesRegistryController,
|
||||
type ServiceStatus,
|
||||
} from "@/domain/services/use-cases/use-services-registry-controller";
|
||||
import type { Service } from "@/domain/services/types/service";
|
||||
import {
|
||||
DeactivateServiceDialog,
|
||||
ServiceDetailsDrawer,
|
||||
|
|
@ -100,53 +101,7 @@ export function ServicesRegistryView() {
|
|||
onAdd={controller.canManage ? controller.openCreate : undefined}
|
||||
/>
|
||||
) : (
|
||||
<Box className="overflow-x-auto rounded-lg border border-border bg-background">
|
||||
<Table aria-label="Services">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Service</TableCell>
|
||||
<TableCell>Trade</TableCell>
|
||||
<TableCell>Completion Document</TableCell>
|
||||
<TableCell>Status</TableCell>
|
||||
<TableCell />
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{controller.filteredServices.map((service) => (
|
||||
<TableRow
|
||||
hover
|
||||
key={String(service.id)}
|
||||
onClick={() => controller.setSelected(service)}
|
||||
sx={{ cursor: "pointer" }}
|
||||
>
|
||||
<TableCell>
|
||||
<Stack direction="row" spacing={1.25} sx={{ alignItems: "center" }}>
|
||||
<Box className="flex size-8 items-center justify-center rounded-md bg-primary/10 text-primary">
|
||||
<ServiceIcon iconKey={service.iconKey} />
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography variant="body2" className="font-semibold">
|
||||
{service.name}
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
{service.supportedWorkOrderTypes.join(" · ")}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
<TableCell>{service.trade}</TableCell>
|
||||
<TableCell>
|
||||
{service.requiresCompletionDocument
|
||||
? service.completionDocTemplate?.name || "No template linked yet"
|
||||
: "Not required"}
|
||||
</TableCell>
|
||||
<TableCell>{service.isActive ? "Active" : "Inactive"}</TableCell>
|
||||
<TableCell align="right">›</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Box>
|
||||
<ServicesTable services={controller.filteredServices} onSelect={controller.setSelected} />
|
||||
)}
|
||||
|
||||
<ServiceDetailsDrawer
|
||||
|
|
@ -182,6 +137,64 @@ export function ServicesRegistryView() {
|
|||
);
|
||||
}
|
||||
|
||||
function ServicesTable({
|
||||
services,
|
||||
onSelect,
|
||||
}: {
|
||||
services: Service[];
|
||||
onSelect: (service: Service) => void;
|
||||
}) {
|
||||
return (
|
||||
<Box className="overflow-x-auto rounded-lg border border-border bg-background">
|
||||
<Table aria-label="Services">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Service</TableCell>
|
||||
<TableCell>Trade</TableCell>
|
||||
<TableCell>Completion Document</TableCell>
|
||||
<TableCell>Status</TableCell>
|
||||
<TableCell />
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{services.map((service) => (
|
||||
<TableRow
|
||||
hover
|
||||
key={String(service.id)}
|
||||
onClick={() => onSelect(service)}
|
||||
sx={{ cursor: "pointer" }}
|
||||
>
|
||||
<TableCell>
|
||||
<Stack direction="row" spacing={1.25} sx={{ alignItems: "center" }}>
|
||||
<Box className="flex size-8 items-center justify-center rounded-md bg-primary/10 text-primary">
|
||||
<ServiceIcon iconKey={service.iconKey} />
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography variant="body2" className="font-semibold">
|
||||
{service.name}
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
{service.supportedWorkOrderTypes.join(" · ")}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
<TableCell>{service.trade}</TableCell>
|
||||
<TableCell>
|
||||
{service.requiresCompletionDocument
|
||||
? service.completionDocTemplate?.name || "No template linked yet"
|
||||
: "Not required"}
|
||||
</TableCell>
|
||||
<TableCell>{service.isActive ? "Active" : "Inactive"}</TableCell>
|
||||
<TableCell align="right">›</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function EmptyServices({
|
||||
search,
|
||||
status,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue