|
- "use client";
-
- import React, { useCallback, useEffect, useMemo, useState } from "react";
- import {
- Box,
- Button,
- Dialog,
- DialogActions,
- DialogContent,
- DialogTitle,
- Stack,
- TextField,
- Typography,
- CircularProgress,
- IconButton,
- TableContainer,
- Table,
- TableHead,
- TableRow,
- TableCell,
- TableBody,
- } from "@mui/material";
- import Delete from "@mui/icons-material/Delete";
- import Add from "@mui/icons-material/Add";
- import { useTranslation } from "react-i18next";
- import { Edit } from "@mui/icons-material";
- import SearchBox, { Criterion } from "@/components/SearchBox/SearchBox";
- import SearchResults, { Column } from "@/components/SearchResults/SearchResults";
- import {
- fetchStockTakeSections,
- updateSectionDescription,
- clearWarehouseSection,
- getWarehousesBySection,
- searchWarehousesForAddToSection,
- editWarehouse,
- } from "@/app/api/warehouse/actions";
- import { WarehouseResult } from "@/app/api/warehouse";
- import { StockTakeSectionInfo } from "@/app/api/warehouse";
- import { deleteDialog, successDialog } from "@/components/Swal/CustomAlerts";
-
- type SearchKey = "stockTakeSection" | "stockTakeSectionDescription";
-
- export default function TabStockTakeSectionMapping() {
- const { t } = useTranslation(["warehouse", "common"]);
- const [sections, setSections] = useState<StockTakeSectionInfo[]>([]);
- const [filteredSections, setFilteredSections] = useState<StockTakeSectionInfo[]>([]);
- const [selectedSection, setSelectedSection] = useState<StockTakeSectionInfo | null>(null);
- const [warehousesInSection, setWarehousesInSection] = useState<WarehouseResult[]>([]);
- const [loading, setLoading] = useState(true);
- const [openDialog, setOpenDialog] = useState(false);
- const [editDesc, setEditDesc] = useState("");
- const [savingDesc, setSavingDesc] = useState(false);
- const [warehouseList, setWarehouseList] = useState<WarehouseResult[]>([]);
- const [openAddDialog, setOpenAddDialog] = useState(false);
- const [addStoreId, setAddStoreId] = useState("");
- const [addWarehouse, setAddWarehouse] = useState("");
- const [addArea, setAddArea] = useState("");
- const [addSlot, setAddSlot] = useState("");
- const [addSearchResults, setAddSearchResults] = useState<WarehouseResult[]>([]);
- const [addSearching, setAddSearching] = useState(false);
- const [addingWarehouseId, setAddingWarehouseId] = useState<number | null>(null);
- const loadSections = useCallback(async () => {
- setLoading(true);
- try {
- const data = await fetchStockTakeSections();
- const withId = (data ?? []).map((s) => ({
- ...s,
- id: s.stockTakeSection,
- }));
- setSections(withId);
- setFilteredSections(withId);
- } catch (e) {
- console.error(e);
- setSections([]);
- setFilteredSections([]);
- } finally {
- setLoading(false);
- }
- }, []);
-
- useEffect(() => {
- loadSections();
- }, [loadSections]);
-
- const handleViewSection = useCallback(async (section: StockTakeSectionInfo) => {
- setSelectedSection(section);
- setEditDesc(section.stockTakeSectionDescription ?? "");
- setOpenDialog(true);
- try {
- const list = await getWarehousesBySection(section.stockTakeSection);
- setWarehousesInSection(list ?? []);
- } catch (e) {
- console.error(e);
- setWarehousesInSection([]);
- }
- }, []);
-
- const criteria: Criterion<SearchKey>[] = useMemo(
- () => [
- { type: "text", label: "Stock Take Section", paramName: "stockTakeSection", placeholder: "" },
- { type: "text", label: "Stock Take Section Description", paramName: "stockTakeSectionDescription", placeholder: "" },
- ],
- []
- );
-
- const handleSearch = useCallback((inputs: Record<SearchKey | `${SearchKey}To`, string>) => {
- const section = (inputs.stockTakeSection ?? "").trim().toLowerCase();
- const desc = (inputs.stockTakeSectionDescription ?? "").trim().toLowerCase();
- setFilteredSections(
- sections.filter(
- (s) =>
- (!section || (s.stockTakeSection ?? "").toLowerCase().includes(section)) &&
- (!desc || (s.stockTakeSectionDescription ?? "").toLowerCase().includes(desc))
- )
- );
- }, [sections]);
-
- const handleReset = useCallback(() => {
- setFilteredSections(sections);
- }, [sections]);
-
- const handleSaveDescription = useCallback(async () => {
- if (!selectedSection) return;
- setSavingDesc(true);
- try {
- await updateSectionDescription(selectedSection.stockTakeSection, editDesc || null);
- await loadSections();
- if (selectedSection) {
- setSelectedSection((prev) => (prev ? { ...prev, stockTakeSectionDescription: editDesc || null } : null));
- }
- successDialog(t("Saved"), t);
- } catch (e) {
- console.error(e);
- } finally {
- setSavingDesc(false);
- }
- }, [selectedSection, editDesc, loadSections, t]);
-
- const handleRemoveWarehouse = useCallback(
- (warehouse: WarehouseResult) => {
- deleteDialog(async () => {
- try {
- await clearWarehouseSection(warehouse.id);
- setWarehousesInSection((prev) => prev.filter((w) => w.id !== warehouse.id));
- successDialog(t("Delete Success"), t);
- } catch (e) {
- console.error(e);
- }
- }, t);
- },
- [t]
- );
- const handleOpenAddWarehouse = useCallback(() => {
- setAddStoreId("");
- setAddWarehouse("");
- setAddArea("");
- setAddSlot("");
- setAddSearchResults([]);
- setOpenAddDialog(true);
- }, []);
-
- const handleAddSearch = useCallback(async () => {
- if (!selectedSection) return;
- setAddSearching(true);
- try {
- const params: { store_id?: string; warehouse?: string; area?: string; slot?: string } = {};
- if (addStoreId.trim()) params.store_id = addStoreId.trim();
- if (addWarehouse.trim()) params.warehouse = addWarehouse.trim();
- if (addArea.trim()) params.area = addArea.trim();
- if (addSlot.trim()) params.slot = addSlot.trim();
- const list = await searchWarehousesForAddToSection(params, selectedSection.stockTakeSection);
- setAddSearchResults(list ?? []);
- } catch (e) {
- console.error(e);
- setAddSearchResults([]);
- } finally {
- setAddSearching(false);
- }
- }, [selectedSection, addStoreId, addWarehouse, addArea, addSlot]);
-
- const handleAddWarehouseToSection = useCallback(
- async (w: WarehouseResult) => {
- if (!selectedSection) return;
- setAddingWarehouseId(w.id);
- try {
- await editWarehouse(w.id, {
- stockTakeSection: selectedSection.stockTakeSection,
- stockTakeSectionDescription: selectedSection.stockTakeSectionDescription ?? undefined,
- });
- setWarehousesInSection((prev) => [...prev, w]);
- setAddSearchResults((prev) => prev.filter((x) => x.id !== w.id));
- successDialog(t("Add Success") ?? t("Saved"), t);
- } catch (e) {
- console.error(e);
- } finally {
- setAddingWarehouseId(null);
- }
- },
- [selectedSection, t]
- );
- const columns = useMemo<Column<StockTakeSectionInfo>[]>(
- () => [
- { name: "stockTakeSection", label: t("stockTakeSection"), align: "left", sx: { width: "25%" } },
- { name: "stockTakeSectionDescription", label: t("stockTakeSectionDescription"), align: "left", sx: { width: "35%" } },
- {
- name: "id",
- label: t("Edit"),
- onClick: (row) => handleViewSection(row),
- buttonIcon: <Edit />,
- buttonIcons: {} as Record<keyof StockTakeSectionInfo, React.ReactNode>,
- color: "primary",
- sx: { width: "20%" },
- },
- ],
- [t, handleViewSection]
- );
-
- if (loading) {
- return (
- <Box sx={{ display: "flex", justifyContent: "center", minHeight: 200, alignItems: "center" }}>
- <CircularProgress />
- </Box>
- );
- }
-
- return (
- <Box>
- <SearchBox<SearchKey> criteria={criteria} onSearch={handleSearch} onReset={handleReset} />
- <SearchResults<StockTakeSectionInfo> items={filteredSections} columns={columns} />
-
- <Dialog open={openDialog} onClose={() => setOpenDialog(false)} maxWidth="md" fullWidth sx={{ zIndex: 1000 }}>
- <DialogTitle>
- {t("Mapping Details")} - {selectedSection?.stockTakeSection} ({selectedSection?.stockTakeSectionDescription ?? ""})
- </DialogTitle>
- <DialogContent>
- <Stack direction="row" alignItems="center" spacing={2} sx={{ mb: 1, minHeight: 40 }}>
- <Typography variant="body2" sx={{ display: "flex", alignItems: "center" }}>
- {t("stockTakeSectionDescription")}
- </Typography>
- <TextField size="small" value={editDesc} onChange={(e) => setEditDesc(e.target.value)} sx={{ minWidth: 200 }} />
- <Button variant="contained" size="small" disabled={savingDesc} onClick={handleSaveDescription}>
- {t("Save")}
- </Button>
- <Box sx={{ flex: 1 }} />
- <Button variant="contained" startIcon={<Add />} onClick={handleOpenAddWarehouse}>
- {t("Add Warehouse")}
- </Button>
- </Stack>
- <TableContainer>
- <Table size="small">
- <TableHead>
- <TableRow>
- <TableCell>{t("code")}</TableCell>
-
- <TableCell>{t("Actions")}</TableCell>
- </TableRow>
- </TableHead>
- <TableBody>
- {warehousesInSection.length === 0 ? (
- <TableRow><TableCell colSpan={3} align="center">{t("No warehouses")}</TableCell></TableRow>
- ) : (
- warehousesInSection.map((w) => (
- <TableRow key={w.id}>
- <TableCell>{w.code}</TableCell>
- <TableCell>
- <IconButton color="error" size="small" onClick={() => handleRemoveWarehouse(w)}>
- <Delete />
- </IconButton>
- </TableCell>
- </TableRow>
- ))
- )}
- </TableBody>
- </Table>
- </TableContainer>
- </DialogContent>
- <DialogActions>
- <Button onClick={() => setOpenDialog(false)}>{t("Cancel")}</Button>
- </DialogActions>
- </Dialog>
- <Dialog open={openAddDialog} onClose={() => setOpenAddDialog(false)} maxWidth="sm" fullWidth sx={{ zIndex: 1000 }}>
- <DialogTitle>{t("Add Warehouse")}</DialogTitle>
- <DialogContent>
- <Stack spacing={2} sx={{ pt: 1 }}>
- <TextField
- size="small"
- label={t("Store ID")}
- value={addStoreId}
- onChange={(e) => setAddStoreId(e.target.value)}
- fullWidth
- />
- <TextField
- size="small"
- label={t("warehouse")}
- value={addWarehouse}
- onChange={(e) => setAddWarehouse(e.target.value)}
- fullWidth
- />
- <TextField
- size="small"
- label={t("area")}
- value={addArea}
- onChange={(e) => setAddArea(e.target.value)}
- fullWidth
- />
- <TextField
- size="small"
- label={t("slot")}
- value={addSlot}
- onChange={(e) => setAddSlot(e.target.value)}
- fullWidth
- />
- <Button variant="contained" onClick={handleAddSearch} disabled={addSearching}>
- {addSearching ? <CircularProgress size={20} /> : t("Search")}
- </Button>
- <TableContainer>
- <Table size="small">
- <TableHead>
- <TableRow>
- <TableCell>{t("code")}</TableCell>
- <TableCell>{t("name")}</TableCell>
- <TableCell>{t("Actions")}</TableCell>
- </TableRow>
- </TableHead>
- <TableBody>
- {addSearchResults
- .filter((w) => !warehousesInSection.some((inc) => inc.id === w.id))
- .map((w) => (
- <TableRow key={w.id}>
- <TableCell>{w.code}</TableCell>
- <TableCell>{w.name}</TableCell>
- <TableCell>
- <Button
- size="small"
- variant="outlined"
- disabled={addingWarehouseId === w.id}
- onClick={() => handleAddWarehouseToSection(w)}
- >
- {addingWarehouseId === w.id ? <CircularProgress size={16} /> : t("Add")}
- </Button>
- </TableCell>
- </TableRow>
- ))}
- </TableBody>
- </Table>
- </TableContainer>
- </Stack>
- </DialogContent>
- <DialogActions>
- <Button onClick={() => setOpenAddDialog(false)}>{t("Cancel")}</Button>
- </DialogActions>
- </Dialog>
- </Box>
- );
- }
|