shoc-frontend-new/src/app/(protected)/settings/dropdowns.tsx
2026-07-24 11:32:58 -03:00

324 lines
10 KiB
TypeScript

import { useMemo, useState } from "react";
import {
Alert,
Box,
Button,
Chip,
CircularProgress,
FormControl,
InputLabel,
MenuItem,
Paper,
Select,
Stack,
TextField,
Typography,
} from "@mui/material";
import { SettingsNav } from "@/components/common/settings-nav";
import {
DROPDOWN_OPTION_CATEGORIES,
type DropdownOption,
type DropdownOptionCategory,
} from "@/domain/settings/dropdown-options/types/dropdown-option";
import { useCreateDropdownOption } from "@/domain/settings/dropdown-options/use-cases/use-create-dropdown-option";
import { useDeleteDropdownOption } from "@/domain/settings/dropdown-options/use-cases/use-delete-dropdown-option";
import { useDropdownOptionsByCategory } from "@/domain/settings/dropdown-options/use-cases/use-dropdown-options-by-category";
import { useUpdateDropdownOption } from "@/domain/settings/dropdown-options/use-cases/use-update-dropdown-option";
export default function DropdownOptionsPage() {
const [activeCategory, setActiveCategory] = useState<DropdownOptionCategory>("Trade");
const [newValue, setNewValue] = useState("");
const [newParent, setNewParent] = useState("");
const [editingId, setEditingId] = useState<string | number | null>(null);
const [editValue, setEditValue] = useState("");
const categoryConfig = useMemo(
() => DROPDOWN_OPTION_CATEGORIES.find((category) => category.key === activeCategory),
[activeCategory],
);
const parentCategory =
categoryConfig && "parent" in categoryConfig ? categoryConfig.parent : undefined;
const { data: options = [], isLoading, error } = useDropdownOptionsByCategory(activeCategory);
const { data: parentOptions = [] } = useDropdownOptionsByCategory("Trade", {
enabled: Boolean(parentCategory),
});
const createMutation = useCreateDropdownOption();
const updateMutation = useUpdateDropdownOption();
const deleteMutation = useDeleteDropdownOption();
const resetFormState = () => {
setNewValue("");
setNewParent("");
setEditingId(null);
setEditValue("");
};
const handleCategoryChange = (category: DropdownOptionCategory) => {
if (category !== activeCategory) {
resetFormState();
}
setActiveCategory(category);
};
const handleAdd = () => {
const value = newValue.trim();
if (!value) {
return;
}
createMutation.mutate(
{
category: activeCategory,
value,
parentValue: parentCategory ? newParent || null : null,
sortOrder: options.length,
isActive: true,
},
{
onSuccess: () => {
setNewValue("");
setNewParent("");
},
},
);
};
const handleEditSave = (item: DropdownOption) => {
const value = editValue.trim();
if (!value) {
return;
}
updateMutation.mutate(
{
id: item.id,
category: item.category,
value,
parentValue: item.parentValue,
sortOrder: item.sortOrder,
isActive: item.isActive,
},
{
onSuccess: () => {
setEditingId(null);
},
},
);
};
const handleDelete = (id: string | number) => {
deleteMutation.mutate(id);
};
const addPlaceholder = `Add new ${categoryConfig?.label.slice(0, -1) ?? "option"}...`;
const isMutating =
createMutation.isPending || updateMutation.isPending || deleteMutation.isPending;
return (
<Box className="flex flex-col gap-4 p-4">
<SettingsNav />
<Stack spacing={0.5}>
<Typography variant="h5" component="h1">
Dropdown Options
</Typography>
<Typography
variant="body2"
sx={{
color: "text.secondary",
}}
>
Manage configurable dropdown values for work orders
</Typography>
</Stack>
<Paper variant="outlined" className="flex min-h-[400px] overflow-hidden">
<Stack className="w-[180px] shrink-0 border-r border-border bg-muted/30" spacing={0}>
{DROPDOWN_OPTION_CATEGORIES.map((category) => (
<Button
key={category.key}
onClick={() => handleCategoryChange(category.key)}
className={`justify-start rounded-none border-b border-border px-4 py-3 text-left normal-case ${
activeCategory === category.key
? "border-r-2 border-r-primary bg-background font-semibold text-primary"
: "font-medium text-muted-foreground hover:bg-muted/50"
}`}
>
{category.label}
</Button>
))}
</Stack>
<Box className="flex flex-1 flex-col gap-4 p-4">
<Stack
direction={{ xs: "column", sm: "row" }}
spacing={2}
sx={{
alignItems: "stretch",
}}
>
{Boolean(parentCategory) && (
<FormControl size="small" className="min-w-[160px]">
<InputLabel id="parent-trade-label">Parent Trade</InputLabel>
<Select
labelId="parent-trade-label"
label="Parent Trade"
value={newParent}
onChange={(event) => setNewParent(event.target.value)}
>
<MenuItem value="">Parent: All</MenuItem>
{parentOptions.map((option) => (
<MenuItem key={String(option.id)} value={option.value}>
{option.value}
</MenuItem>
))}
</Select>
</FormControl>
)}
<TextField
size="small"
value={newValue}
onChange={(event) => setNewValue(event.target.value)}
onKeyDown={(event) => {
if (event.key === "Enter") {
event.preventDefault();
handleAdd();
}
}}
placeholder={addPlaceholder}
className="flex-1"
/>
<Button
variant="contained"
onClick={handleAdd}
disabled={!newValue.trim() || isMutating}
className="shrink-0"
>
Add
</Button>
</Stack>
{Boolean(error) && (
<Alert severity="error">
{error instanceof Error ? error.message : "Failed to load options"}
</Alert>
)}
{isLoading ? (
<Stack
className="py-10"
sx={{
alignItems: "center",
}}
>
<CircularProgress size={28} />
</Stack>
) : options.length === 0 ? (
<Typography
align="center"
className="py-10"
sx={{
color: "text.secondary",
}}
>
No options configured yet
</Typography>
) : (
<Stack spacing={1}>
{options.map((item) => (
<Paper
key={String(item.id)}
variant="outlined"
className="flex items-center justify-between gap-3 px-3 py-2"
>
{editingId === item.id ? (
<TextField
size="small"
value={editValue}
onChange={(event) => setEditValue(event.target.value)}
onKeyDown={(event) => {
if (event.key === "Enter") {
handleEditSave(item);
}
if (event.key === "Escape") {
setEditingId(null);
}
}}
autoFocus
className="flex-1"
/>
) : (
<Stack
direction="row"
spacing={1}
className="min-w-0"
sx={{
alignItems: "center",
}}
>
<Typography variant="body2" className="truncate">
{item.value}
</Typography>
{Boolean(item.parentValue) && (
<Chip size="small" label={item.parentValue} variant="outlined" />
)}
</Stack>
)}
<Stack direction="row" spacing={1} className="shrink-0">
{editingId === item.id ? (
<>
<Button
size="small"
variant="contained"
color="success"
onClick={() => handleEditSave(item)}
disabled={isMutating}
>
Save
</Button>
<Button
size="small"
variant="outlined"
color="inherit"
onClick={() => setEditingId(null)}
>
Cancel
</Button>
</>
) : (
<>
<Button
size="small"
variant="outlined"
onClick={() => {
setEditingId(item.id);
setEditValue(item.value);
}}
>
Edit
</Button>
<Button
size="small"
variant="outlined"
color="error"
onClick={() => handleDelete(item.id)}
disabled={isMutating}
>
Delete
</Button>
</>
)}
</Stack>
</Paper>
))}
</Stack>
)}
</Box>
</Paper>
</Box>
);
}