mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-04 04:23:13 +00:00
324 lines
10 KiB
TypeScript
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>
|
|
);
|
|
}
|