Explorar el Código

Update visual appearance of each stock take round

stockTakeUpdate
Harry Groves hace 1 semana
padre
commit
212bb2300b
Se han modificado 4 ficheros con 113 adiciones y 96 borrados
  1. +12
    -26
      src/components/StockTakeManagement/PickerCardList.tsx
  2. +78
    -57
      src/components/StockTakeManagement/StockTakeRoundCardList.tsx
  3. +12
    -7
      src/i18n/en/stockTake.json
  4. +11
    -6
      src/i18n/zh/stockTake.json

+ 12
- 26
src/components/StockTakeManagement/PickerCardList.tsx Ver fichero

@@ -71,6 +71,7 @@ import {
dayjsToDateTimeString,
} from "@/app/utils/formatUtil";
import { AUTH } from "@/authorities";
import { statusChipColor } from "./StockTakeRoundCardList";

const FLOOR_GROUP_UNSET = "__UNSET__";
/** 與 controllQRcodescanWarehouse 選倉相同:樓層 → 倉庫 → 倉位 */
@@ -1264,21 +1265,6 @@ const PickerCardList: React.FC<PickerCardListProps> = ({
return Array.from(sectionSet).sort((a, b) => a.localeCompare(b));
});
}, [roundRecords]);
const getStatusColor = (status: string) => {
const statusLower = status.toLowerCase().replace(/_/g, "");
if (statusLower === "completed") return "success";
if (
statusLower === "stocktaking" ||
statusLower === "inprogress" ||
statusLower === "processing"
) {
return "primary"; // 盤點中 — 藍
}
if (statusLower === "approving") return "info";
if (statusLower === "pending") return "warning"; // 待處理 — 橘
if (statusLower === "nocycle") return "default";
return "default";
};
const TimeDisplay: React.FC<{ startTime: string | null; endTime: string | null }> = ({ startTime, endTime }) => {
const [currentTime, setCurrentTime] = useState(dayjs());
@@ -1339,10 +1325,6 @@ const PickerCardList: React.FC<PickerCardListProps> = ({
return "-";
}
};
const getCompletionRate = (session: AllPickedStockTakeListReponse): number => {
if (session.totalInventoryLotNumber === 0) return 0;
return Math.round((session.currentStockTakeItemNumber / session.totalInventoryLotNumber) * 100);
};
const planStartDate = globalRoundPlanStartDate;
const closeCreateDialog = () => {
setOpenConfirmDialog(false);
@@ -1732,16 +1714,14 @@ const PickerCardList: React.FC<PickerCardListProps> = ({
) : (
<Grid container spacing={2}>
{stockTakeSessions.map((session: AllPickedStockTakeListReponse) => {
const statusColor = getStatusColor(session.status || "");
const lastStockTakeDate = session.lastStockTakeDate
? dayjs(session.lastStockTakeDate).format(OUTPUT_DATE_FORMAT)
: "-";
const completionRate = getCompletionRate(session);
const sectionMeta = [
session.stockTakeSectionDescription,
session.warehouseArea,
session.storeId,
].filter((v): v is string => Boolean(v && v.trim())).join(" / ");
].filter((v): v is string => Boolean(v && v.trim())).join(" · ");

return (
<Grid key={session.id} item xs={12} md={6}>
@@ -1751,7 +1731,7 @@ const PickerCardList: React.FC<PickerCardListProps> = ({
display: "flex",
flexDirection: "column",
border: "1px solid",
borderColor: statusColor === "success" ? "success.main" : "primary.main",
borderColor: "divider",
}}
>
<CardContent sx={{ pb: 1, flexGrow: 1 }}>
@@ -1792,7 +1772,8 @@ const PickerCardList: React.FC<PickerCardListProps> = ({
{t("View Details")}
</Button>
<Button
variant="contained"
variant="outlined"
color="inherit"
sx={{ minHeight: 48, px: 2 }}
onClick={() => onReStockTakeClick(session)}
disabled={!session.reStockTakeTrueFalse}
@@ -1802,9 +1783,14 @@ const PickerCardList: React.FC<PickerCardListProps> = ({
</Stack>
<Stack direction="row" spacing={0.5} flexWrap="wrap" justifyContent="flex-end">
{session.reStockTakeTrueFalse ? (
<Chip size="small" label={t("notMatch")} color="warning" />
<Chip size="small" variant="outlined" color="warning" label={t("notMatch")} />
) : null}
<Chip size="small" label={t(session.status || "")} color={statusColor as any} />
<Chip
size="small"
variant="outlined"
color={statusChipColor(session.status || "")}
label={t(session.status || "")}
/>
</Stack>
</CardActions>
</Card>


+ 78
- 57
src/components/StockTakeManagement/StockTakeRoundCardList.tsx Ver fichero

@@ -11,6 +11,7 @@ import {
CardContent,
Chip,
CircularProgress,
LinearProgress,
Dialog,
DialogActions,
DialogContent,
@@ -205,13 +206,32 @@ const StockTakeRoundCardList: React.FC<StockTakeRoundCardListProps> = ({
return `${t("Stock Take Round")} #${card.stockTakeRoundId}`;
};

const statusColor = (status: string) => {
const s = (status || "").toLowerCase().replace(/_/g, "");
if (s === "completed") return "success";
if (s === "approving") return "info";
if (s === "stocktaking" || s === "processing") return "primary";
if (s === "pending") return "warning";
return "default";
const progressRow = (label: string, done: number, total: number, barColor: "primary.main" | "success.main") => {
const pct = total > 0 ? Math.min(100, (done / total) * 100) : 0;
return (
<Box>
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", gap: 1 }}>
<Typography variant="caption" color="text.secondary">
{label}
</Typography>
<Typography variant="body2" fontWeight={600} sx={{ fontVariantNumeric: "tabular-nums" }}>
{t("Count of total", { done, total })}
</Typography>
</Box>
<LinearProgress
variant="determinate"
value={pct}
aria-label={`${label} ${t("Count of total", { done, total })}`}
sx={{
mt: 0.4,
height: 4,
borderRadius: 99,
bgcolor: "action.hover",
"& .MuiLinearProgress-bar": { borderRadius: 99, bgcolor: barColor },
}}
/>
</Box>
);
};

return (
@@ -309,9 +329,8 @@ const StockTakeRoundCardList: React.FC<StockTakeRoundCardListProps> = ({
const total = isItemRound ? (card.totalInventoryLotNumber || 0) : (card.sectionCount || 0);
const taken = card.stockTakenCount || 0;
const approved = card.approvedCount || 0;
const progressLegend = isItemRound
? t("Round progress legend lots")
: t("Round progress legend");
const floor = card.mainFloor?.trim();
const modeLabel = isItemRound ? t("Create by item") : t("Create by section");
return (
<Grid key={card.stockTakeRoundId} item xs={12} sm={6} md={4}>
<Card
@@ -320,64 +339,54 @@ const StockTakeRoundCardList: React.FC<StockTakeRoundCardListProps> = ({
display: "flex",
flexDirection: "column",
border: "1px solid",
borderColor: card.open ? "primary.main" : "success.main",
borderColor: "divider",
}}
>
<CardContent sx={{ pb: 1, flexGrow: 1 }}>
<Stack direction="row" spacing={1} alignItems="center" sx={{ mb: 0.5 }}>
<Typography variant="subtitle1" fontWeight={600}>
{roundLabel(card)}
</Typography>
{card.stockTakeRoundMode === "item" ? (
<Chip size="small" label={t("Create by item")} color="info" />
) : null}
<Stack direction="row" spacing={1} alignItems="flex-start" justifyContent="space-between">
<Box sx={{ minWidth: 0 }}>
<Typography variant="subtitle1" fontWeight={600} noWrap>
{roundLabel(card)}
</Typography>
<Typography variant="body2" color="text.secondary">
{floor ? `${modeLabel} · ${floor}` : modeLabel}
</Typography>
</Box>
<Chip
size="small"
variant="outlined"
color={statusChipColor(card.status)}
label={t(card.status || " ")}
sx={{ flexShrink: 0 }}
/>
</Stack>
<Typography variant="body2" color="text.secondary">
<Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
{t("Plan Start Date")}:{" "}
{card.planStartDate ? dayjs(card.planStartDate).format(OUTPUT_DATE_FORMAT) : "-"}
</Typography>
<Typography variant="body2" color="text.secondary">
{t("Main floor")}: {card.mainFloor?.trim() || "-"}
<Typography variant="caption" color="text.secondary" sx={{ display: "block", mt: 1.5 }}>
{isItemRound ? t("Round unit lots") : t("Round unit sections")}
</Typography>
<Stack spacing={0.25}>
<Typography variant="body2" color="text.secondary" noWrap>
{progressLegend}
</Typography>
<Typography
fontWeight={700}
sx={{
fontSize: "1.5rem",
lineHeight: 1.2,
fontVariantNumeric: "tabular-nums",
letterSpacing: 0.5,
}}
>
<Box component="span" sx={{ color: "primary.main" }}>{taken}</Box>
<Box component="span" sx={{ color: "text.disabled", mx: 0.75 }}>/</Box>
<Box component="span" sx={{ color: "success.main" }}>{approved}</Box>
<Box component="span" sx={{ color: "text.disabled", mx: 0.75 }}>/</Box>
<Box component="span" sx={{ color: "text.primary" }}>{total}</Box>
</Typography>
<Stack spacing={1} sx={{ mt: 0.5 }}>
{progressRow(t("progressCounted"), taken, total, "primary.main")}
{progressRow(t("progressApproved"), approved, total, "success.main")}
</Stack>
</CardContent>
<CardActions sx={{ pt: 0.5, justifyContent: "space-between", flexWrap: "wrap", gap: 1 }}>
<Stack direction="row" spacing={1}>
<Button size="small" variant="contained" onClick={() => onEnterRound(card, rounds)}>
{t("View Details")}
<CardActions sx={{ pt: 0.5, gap: 1 }}>
<Button size="small" variant="contained" onClick={() => onEnterRound(card, rounds)}>
{t("View Details")}
</Button>
{isAdmin && card.open ? (
<Button
size="small"
variant="outlined"
color="warning"
disabled={closingId === card.stockTakeRoundId}
onClick={() => setPendingCloseCard(card)}
>
{closingId === card.stockTakeRoundId ? <CircularProgress size={16} /> : t("Close round")}
</Button>
{isAdmin && card.open ? (
<Button
size="small"
variant="outlined"
color="warning"
disabled={closingId === card.stockTakeRoundId}
onClick={() => setPendingCloseCard(card)}
>
{closingId === card.stockTakeRoundId ? <CircularProgress size={16} /> : t("Close round")}
</Button>
) : null}
</Stack>
<Chip size="small" label={t(card.status || " ")} color={statusColor(card.status) as "success" | "info" | "primary" | "warning"} />
) : null}
</CardActions>
</Card>
</Grid>
@@ -420,4 +429,16 @@ const StockTakeRoundCardList: React.FC<StockTakeRoundCardListProps> = ({
);
};

type StatusChipColor = "default" | "primary" | "secondary" | "error" | "info" | "success" | "warning";

export function statusChipColor(status: string): StatusChipColor {
const s = (status || "").toLowerCase().replace(/_/g, "");
if (s === "completed") return "success";
if (s === "pending") return "warning";
if (s === "approving") return "info";
if (s === "stocktaking" || s === "processing" || s === "inprogress") return "primary";
if (s === "notmatch") return "warning";
return "default";
}

export default StockTakeRoundCardList;

+ 12
- 7
src/i18n/en/stockTake.json Ver fichero

@@ -32,6 +32,11 @@
"Month suffix": "",
"Next month": "Next month",
"Open rounds this month": "Open rounds",
"progressCounted": "Counted",
"progressApproved": "Approved",
"Count of total": "{{done}} of {{total}}",
"Round unit sections": "Sections",
"Round unit lots": "Lots",
"Previous month": "Previous month",
"Round closed": "Round closed",
"Main floor": "Main floor",
@@ -59,7 +64,7 @@
"Confirm create stock take": "Confirm create stock take",
"Section create item overlap title": "These items are already in an open item stock-take round; this round will skip them",
"Section create item overlap line": "Item {{item}} is in round #{{round}}{{roundName}}; this round will not include it (section {{section}})",
"Control Time": "Control Time",
"Control Time": "Time spent",
"Create by item": "By item",
"Create by section": "By section",
"Cannot create while both section and item rounds are open": "Cannot create a new round while both section and item rounds are open",
@@ -77,7 +82,7 @@
"Total selected lots label": "Selected lots",
"items unit": "item(s)",
"lots unit": "lot(s)",
"Create Stock Take (Select Sections)": "Create stock take (select sections)",
"Create Stock Take (Select Sections)": "Create stock take",
"Created": "Created",
"Creation date": "Creation date",
"Damage": "Damage",
@@ -179,11 +184,11 @@
"Stock take round name": "Stock take round name",
"Stock take round name placeholder": "Optional, e.g. May full-site stock take",
"Stock take sections in current list": "{{count}} stock take section(s) in current list",
"Store ID": "Store ID",
"Store ID": "Floor",
"Submit All Inputted": "Submit All Inputted",
"Total": "Total",
"Total Adjustments": "Total Adjustments",
"Total Item Kind Number": "Total Item Kind Number",
"Total Item Kind Number": "Items",
"Total Items": "Total Items",
"Total Lots": "Total Lots",
"Total Sections": "Total Sections",
@@ -196,7 +201,7 @@
"Variance filter inclusive only": "Within range",
"Variance filter inclusive range hint": "Show rows with |variance %| ≤ {{value}}%",
"View Details": "View Details",
"View ReStockTake": "View ReStockTake",
"View ReStockTake": "Re-count",
"Warehouse": "Warehouse",
"Warehouse code": "Warehouse code",
"Warehouse Location": "Warehouse Location",
@@ -211,7 +216,7 @@
"book qty": "book qty",
"completed": "Completed",
"do": "Delivery order",
"end time": "end time",
"end time": "Finished",
"jo": "Job order",
"lot_issue": "Lot issue",
"material": "Material",
@@ -224,7 +229,7 @@
"resolved": "Resolved",
"sections unit": "area(s)",
"selected stock take qty": "selected stock take qty",
"start time": "start time",
"start time": "Started",
"Select floor first": "Select floor first",
"Select warehouse first": "Select warehouse first",
"Select area first": "Select area first",


+ 11
- 6
src/i18n/zh/stockTake.json Ver fichero

@@ -15,6 +15,11 @@
"Month suffix": "月",
"Next month": "下一個月",
"Open rounds this month": "進行中的輪次",
"progressCounted": "已點算",
"progressApproved": "已審核",
"Count of total": "{{done}}/{{total}}",
"Round unit sections": "區域",
"Round unit lots": "批號",
"Previous month": "上一個月",
"Round closed": "已關閉此輪",
"Main floor": "主要樓層",
@@ -74,9 +79,9 @@
"Last Stock Take Date": "上次盤點日期",
"Stock Taker": "盤點員",
"Approver": "審核員",
"start time": "開始時間",
"end time": "結束時間",
"Control Time": "操作時間",
"start time": "開始",
"end time": "結束",
"Control Time": "耗時",
"Approver stock take record saved successfully": "審核員盤點記錄保存成功",
"Stock take record status updated to not match": "盤點狀態更新為要求重點",
"Batch approver save completed: {{success}} success, {{errors}} errors": "批次審核儲存完成:成功 {{success}} 筆,錯誤 {{errors}} 筆",
@@ -158,9 +163,9 @@
"Total selected lots label": "已選批號",
"items unit": "項",
"lots unit": "筆",
"Create Stock Take (Select Sections)": "建立盤點(選擇區域)",
"Total Item Kind Number": "貨品種類數量",
"View ReStockTake": "查看重新盤點",
"Create Stock Take (Select Sections)": "建立盤點",
"Total Item Kind Number": "貨品種類",
"View ReStockTake": "重新盤點",
"Warehouse missing stock take section tooltip has": "有 {{count}} 個倉庫未設定盤點區域,點擊查看",
"Warehouse missing stock take section tooltip none": "所有倉庫均已設定盤點區域",
"Warehouse missing stock take section warn title": "未設定盤點區域的倉庫",


Cargando…
Cancelar
Guardar