#
vincentlu
2026-01-10 1bc33546a044cbc84dd9595c19dbcd9a4e309fc9
zy-acs-flow/src/page/sta/StaPanel.jsx
@@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react";
import React, { useState, useEffect, useMemo } from "react";
import {
    Box,
    Card,
@@ -13,11 +13,16 @@
    Divider,
    CircularProgress,
    Stack,
    FormControl,
    InputLabel,
    Select,
    MenuItem,
} from '@mui/material';
import {
    useTranslate,
    useRecordContext,
    useDataProvider,
    useGetMany,
} from 'react-admin';
import { format } from 'date-fns';
import * as Common from '@/utils/common'
@@ -30,23 +35,60 @@
];
const INFO_FIELDS = [
    { labelKey: 'table.field.sta.uuid', valueKey: 'uuid' },
    { labelKey: 'table.field.sta.zoneId', valueKey: 'zoneId$' },
    { labelKey: 'table.field.sta.staType', valueKey: 'staType$' },
    { labelKey: 'table.field.sta.code', valueKey: 'code$' },
    { labelKey: 'table.field.sta.capacity', valueKey: 'capacity' },
    { labelKey: 'table.field.sta.offset', valueKey: 'offset' },
    { labelKey: 'table.field.sta.rsvInCnt', valueKey: 'rsvInCnt' },
    { labelKey: 'table.field.sta.rsvOutCnt', valueKey: 'rsvOutCnt' },
    { labelKey: 'table.field.sta.staSts', valueKey: 'staSts$' },
    { labelKey: 'table.field.sta.offset', valueKey: 'offset' },
    { labelKey: 'table.field.sta.zpallet', valueKey: 'zpallet' },
];
const RESERVE_COLUMN_COUNT = 11;
const RESERVE_TYPE_OPTIONS = ['IN', 'OUT'];
const RESERVE_STATE_OPTIONS = ['reserved', 'waiting', 'confirmed', 'canceled', 'timeout'];
const StaPanel = () => {
    const record = useRecordContext();
    const translate = useTranslate();
    const dataProvider = useDataProvider();
    const [reserves, setReserves] = useState([]);
    const [isReservesLoading, setIsReservesLoading] = useState(false);
    const [typeFilter, setTypeFilter] = useState('');
    const [stateFilter, setStateFilter] = useState('');
    const taskIds = useMemo(() => extractIds(reserves, 'taskId'), [reserves]);
    const segmentIds = useMemo(() => extractIds(reserves, 'segmentId'), [reserves]);
    const agvIds = useMemo(() => extractIds(reserves, 'agvId'), [reserves]);
    const { data: taskRecords = [] } = useGetMany('task', { ids: taskIds }, { enabled: taskIds.length > 0 });
    const { data: segmentRecords = [] } = useGetMany('segment', { ids: segmentIds }, { enabled: segmentIds.length > 0 });
    const { data: agvRecords = [] } = useGetMany('agv', { ids: agvIds }, { enabled: agvIds.length > 0 });
    const taskLabelMap = useMemo(
        () => createLabelMap(taskRecords, (item) => item.seqNum),
        [taskRecords]
    );
    const segmentLabelMap = useMemo(
        () => createLabelMap(segmentRecords, (item) => item.groupId + '-' + item.serial),
        [segmentRecords]
    );
    const agvLabelMap = useMemo(
        () => createLabelMap(agvRecords, (item) => item.uuid),
        [agvRecords]
    );
    const filteredReserves = useMemo(() => {
        if (!typeFilter && !stateFilter) {
            return reserves;
        }
        return reserves.filter((reserve) => {
            const matchesType =
                !typeFilter || normalizeValueKey(reserve?.type) === normalizeValueKey(typeFilter);
            const matchesState =
                !stateFilter || normalizeValueKey(reserve?.state) === normalizeValueKey(stateFilter);
            return matchesType && matchesState;
        });
    }, [reserves, typeFilter, stateFilter]);
    useEffect(() => {
        if (!record?.id) {
@@ -59,21 +101,18 @@
        setIsReservesLoading(true);
        dataProvider.getList('staReserve', {
            pagination: { page: 1, perPage: 10 },
            sort: { field: 'updateTime', order: 'DESC' },
            sort: { field: 'updateTime', order: 'desc' },
            filter: { staId: record.id },
        })
            .then(({ data }) => {
                if (!isMounted) return;
                setReserves(data || []);
            })
            .catch(() => {
                if (!isMounted) return;
                setReserves([]);
            })
            .finally(() => {
                if (!isMounted) return;
                setIsReservesLoading(false);
            });
        }).then(({ data }) => {
            if (!isMounted) return;
            setReserves(data || []);
        }).catch(() => {
            if (!isMounted) return;
            setReserves([]);
        }).finally(() => {
            if (!isMounted) return;
            setIsReservesLoading(false);
        });
        return () => {
            isMounted = false;
@@ -84,11 +123,11 @@
    return (
        <>
            <Card sx={{ width: { xs: 320, sm: 560, md: 680, lg: 900 }, margin: 'auto', mt: .5, mb: .5 }}>
            <Card sx={{ maxWidth: '80%', margin: 'auto', mt: .5, mb: .5 }}>
                <CardContent>
                    <Grid container spacing={2}>
                        <Grid item xs={12} sx={{ display: 'flex', justifyContent: 'space-between' }}>
                            <Typography variant="h6" gutterBottom align="left" sx={{
                            <Typography variant="subtitle2" gutterBottom align="left" sx={{
                                maxWidth: { xs: '140px', sm: '220px', md: '300px', lg: '360px' },
                                whiteSpace: 'nowrap',
                                overflow: 'hidden',
@@ -96,19 +135,14 @@
                            }}>
                                {Common.camelToPascalWithSpaces(translate('table.field.sta.staNo'))}: {record.staNo}
                            </Typography>
                            <Typography variant="h6" gutterBottom align="right" >
                                ID: {record.id}
                            </Typography>
                        </Grid>
                    </Grid>
                    <Grid container spacing={2}>
                        <Grid item xs={12} container alignContent="flex-end">
                            <Typography variant="caption" color="textSecondary" sx={{ wordWrap: 'break-word', wordBreak: 'break-all' }}>
                                {Common.camelToPascalWithSpaces(translate('common.field.memo'))}:{record.memo || '-'}
                            </Typography>
                        </Grid>
                    </Grid>
                    <Box height={16}>&nbsp;</Box>
                    <Box height={12}>&nbsp;</Box>
                    <Grid container spacing={2}>
                        {INFO_FIELDS.map(({ labelKey, valueKey }) => (
                            <Grid item xs={12} sm={6} md={4} key={labelKey}>
@@ -122,9 +156,9 @@
                    <Divider sx={{ my: 2 }} />
                    <Typography variant="subtitle2" color="textSecondary" gutterBottom>
                        {translate('common.field.status')} / {translate('table.field.sta.staNo')}
                    </Typography>
                    {/* <Typography variant="subtitle2" color="textSecondary" gutterBottom>
                        {translate('common.field.status')}
                    </Typography> */}
                    <Grid container spacing={2}>
                        {STATUS_FIELDS.map(({ key, labelKey }) => (
                            <Grid item xs={6} sm={3} key={key}>
@@ -132,29 +166,74 @@
                            </Grid>
                        ))}
                    </Grid>
                    <Divider sx={{ my: 2 }} />
                    <Box>
                        <Typography variant="subtitle1" gutterBottom>
                            {translate('menu.staReserve')}
                        </Typography>
                        <Stack direction={{ xs: 'column', sm: 'row' }} spacing={2} mb={2}>
                            <FormControl size="small" sx={{ minWidth: 160 }}>
                                <InputLabel id="sta-panel-reserve-type-label">
                                    {translate('table.field.staReserve.type')}
                                </InputLabel>
                                <Select
                                    labelId="sta-panel-reserve-type-label"
                                    value={typeFilter}
                                    label={translate('table.field.staReserve.type')}
                                    onChange={(event) => setTypeFilter(event.target.value)}
                                >
                                    <MenuItem value="">
                                        {translate('common.action.deselect')}
                                    </MenuItem>
                                    {RESERVE_TYPE_OPTIONS.map((option) => (
                                        <MenuItem key={option} value={option}>
                                            {formatReserveType(option, translate)}
                                        </MenuItem>
                                    ))}
                                </Select>
                            </FormControl>
                            <FormControl size="small" sx={{ minWidth: 160 }}>
                                <InputLabel id="sta-panel-reserve-state-label">
                                    {translate('table.field.staReserve.state')}
                                </InputLabel>
                                <Select
                                    labelId="sta-panel-reserve-state-label"
                                    value={stateFilter}
                                    label={translate('table.field.staReserve.state')}
                                    onChange={(event) => setStateFilter(event.target.value)}
                                >
                                    <MenuItem value="">
                                        {translate('common.action.deselect')}
                                    </MenuItem>
                                    {RESERVE_STATE_OPTIONS.map((option) => (
                                        <MenuItem key={option} value={option}>
                                            {formatReserveState(option, translate)}
                                        </MenuItem>
                                    ))}
                                </Select>
                            </FormControl>
                        </Stack>
                        <Table size="small">
                            <TableHead>
                                <TableRow>
                                    <TableCell>{translate('table.field.staReserve.name')}</TableCell>
                                    <TableCell>{translate('table.field.staReserve.uuid')}</TableCell>
                                    <TableCell>{translate('table.field.staReserve.taskId')}</TableCell>
                                    <TableCell>{translate('table.field.staReserve.segmentId')}</TableCell>
                                    <TableCell>{translate('table.field.staReserve.agvId')}</TableCell>
                                    <TableCell>{translate('table.field.staReserve.type')}</TableCell>
                                    <TableCellRight>{translate('table.field.staReserve.qty')}</TableCellRight>
                                    <TableCell>{translate('table.field.staReserve.state')}</TableCell>
                                    <TableCell>{translate('table.field.staReserve.agvId')}</TableCell>
                                    <TableCell>{translate('table.field.staReserve.expireTime')}</TableCell>
                                    <TableCell>{translate('table.field.staReserve.waitingAt')}</TableCell>
                                    <TableCell>{translate('table.field.staReserve.confirmedAt')}</TableCell>
                                    <TableCell>{translate('table.field.staReserve.uniqKey')}</TableCell>
                                </TableRow>
                            </TableHead>
                            <TableBody>
                                {isReservesLoading && (
                                    <TableRow>
                                        <TableCell colSpan={7}>
                                        <TableCell colSpan={RESERVE_COLUMN_COUNT}>
                                            <Box display="flex" alignItems="center" gap={1}>
                                                <CircularProgress size={16} />
                                                <Typography variant="body2" color="textSecondary">
@@ -164,24 +243,28 @@
                                        </TableCell>
                                    </TableRow>
                                )}
                                {!isReservesLoading && reserves.length === 0 && (
                                {!isReservesLoading && filteredReserves.length === 0 && (
                                    <TableRow>
                                        <TableCell colSpan={7}>
                                        <TableCell colSpan={RESERVE_COLUMN_COUNT}>
                                            <Typography variant="body2" color="textSecondary">
                                                {translate('ra.navigation.no_results')}
                                            </Typography>
                                        </TableCell>
                                    </TableRow>
                                )}
                                {reserves.map((reserve) => (
                                {filteredReserves.map((reserve) => (
                                    <TableRow key={reserve.id}>
                                        <TableCell>{reserve.name || '-'}</TableCell>
                                        <TableCell>{reserve.uuid || '-'}</TableCell>
                                        <TableCell>{getReferenceLabel(taskLabelMap, reserve.taskId)}</TableCell>
                                        <TableCell>{getReferenceLabel(segmentLabelMap, reserve.segmentId)}</TableCell>
                                        <TableCell>{getReferenceLabel(agvLabelMap, reserve.agvId)}</TableCell>
                                        <TableCell>{formatReserveType(reserve.type, translate)}</TableCell>
                                        <TableCellRight>{reserve.qty ?? '-'}</TableCellRight>
                                        <TableCell>{formatReserveState(reserve.state, translate)}</TableCell>
                                        <TableCell>{reserve.agvId$ || reserve.agvId || '-'}</TableCell>
                                        <TableCell>{formatDateTime(reserve.expireTime)}</TableCell>
                                        <TableCell>{formatDateTime(reserve.waitingAt)}</TableCell>
                                        <TableCell>{formatDateTime(reserve.confirmedAt)}</TableCell>
                                        <TableCell>{reserve.uniqKey || '-'}</TableCell>
                                    </TableRow>
                                ))}
                            </TableBody>
@@ -200,7 +283,7 @@
            <Typography variant="caption" color="textSecondary">
                {Common.camelToPascalWithSpaces(translate(labelKey))}
            </Typography>
            <Typography variant="body2" fontWeight={600}>
            <Typography variant="body2" fontWeight={400}>
                {formatInfoValue(value)}
            </Typography>
        </Stack>
@@ -240,7 +323,7 @@
                    height: 14,
                    borderRadius: '50%',
                    backgroundColor: color,
                    boxShadow: `0 0 6px ${color}`,
                    boxShadow: `0 0 3px ${color}`,
                    border: '1px solid rgba(0,0,0,0.12)'
                }} />
                <Typography variant="body2">
@@ -258,7 +341,7 @@
    if (isFalsy(value)) {
        return '#9e9e9e';
    }
    return '#ff9800';
    return '#d40000ff';
};
const isTruthy = (value) => {
@@ -288,6 +371,37 @@
    return String(value);
};
const extractIds = (items, key) => {
    if (!items || items.length === 0) return [];
    const unique = new Set();
    items.forEach((item) => {
        const value = item?.[key];
        if (value !== undefined && value !== null && value !== '') {
            unique.add(value);
        }
    });
    return Array.from(unique);
};
const createLabelMap = (records, getLabel) => {
    if (!records || records.length === 0) return {};
    return records.reduce((acc, record) => {
        if (record?.id === undefined || record?.id === null) {
            return acc;
        }
        const label = getLabel(record);
        acc[record.id] = label ?? record.id;
        return acc;
    }, {});
};
const getReferenceLabel = (map, id) => {
    if (id === undefined || id === null || id === '') {
        return '-';
    }
    return map?.[id] || '-';
};
const formatReserveType = (value, translate) =>
    formatReserveEnum(value, translate, 'type');
@@ -296,6 +410,11 @@
        cancelled: 'canceled',
    });
const getReserveRelationValue = (reserve, key) => {
    if (!reserve) return '-';
    return reserve[`${key}$`] || reserve[key] || '-';
};
const formatReserveEnum = (value, translate, enumType, overrides = {}) => {
    const normalized = normalizeValueKey(value);
    if (!normalized) {