feat: Enhance user management and reporting features

- Added `end_date` field to user management for better tracking of user status.
- Updated SQL queries in `adminController` to include `end_date` during user save operations.
- Improved purchase report data handling by refining the logic for received quantities.
- Enhanced file preview functionality to streamline file path handling.
- Updated outbound and receiving controllers to ensure accurate updates to shipment and purchase order details.

These changes aim to improve the overall functionality and user experience in managing user data and reporting processes.
This commit is contained in:
kjs
2026-04-08 15:33:09 +09:00
parent 3421d95e4e
commit 2a23cadb41
124 changed files with 5541 additions and 5662 deletions
@@ -12,7 +12,7 @@
* - 납품처 등록 (delivery_destination)
*/
import React, { useState, useEffect, useCallback, useRef } from "react";
import React, { useState, useEffect, useCallback, useRef, useMemo } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
@@ -820,12 +820,14 @@ export default function CustomerManagementPage() {
const allItems = res.data?.data?.data || res.data?.data?.rows || [];
setItemTotalCount(allItems.length);
const existingItemIds = new Set(priceItems.map((p: any) => p.item_id || p.item_number));
const SALES_CODES = ["CAT_ML8ZFVEL_1TOR"]; // 영업관리 카테고리 코드
setItemSearchResults(allItems.filter((item: any) => {
const seenNumbers = new Set<string>();
const deduped = allItems.filter((item: any) => {
if (existingItemIds.has(item.item_number) || existingItemIds.has(item.id)) return false;
const divCodes = (item.division || "").split(",").map((c: string) => c.trim());
return divCodes.some((code: string) => SALES_CODES.includes(code));
}));
if (item.item_number && seenNumbers.has(item.item_number)) return false;
if (item.item_number) seenNumbers.add(item.item_number);
return true;
});
setItemSearchResults(deduped);
} catch { /* skip */ } finally { setItemSearchLoading(false); }
};
@@ -1229,47 +1231,44 @@ export default function CustomerManagementPage() {
}
};
// 컬럼 가시성 헬퍼
const isColumnVisible = (key: string) => ts.isVisible(key);
const customerColSpan = 1 + ["customer_code", "customer_name", "contact_person", "contact_phone", "division", "status"]
.filter((k) => isColumnVisible(k)).length;
// EDataTable 컬럼 정의 (거래처 목록)
const customerColumns: EDataTableColumn[] = [
...(isColumnVisible("customer_code") ? [{ key: "customer_code", label: "거래처코드", width: "w-[120px]" }] : []),
...(isColumnVisible("customer_name") ? [{ key: "customer_name", label: "거래처명", minWidth: "min-w-[140px]" }] : []),
...(isColumnVisible("division") ? [{
key: "division",
label: "거래유형",
width: "w-[80px]",
render: (val: any) =>
val ? (
<Badge variant="secondary" className="text-[10px] px-1.5 py-0 h-5 font-normal">
{val}
</Badge>
) : null,
}] : []),
...(isColumnVisible("contact_person") ? [{ key: "contact_person", label: "담당자", width: "w-[80px]" }] : []),
...(isColumnVisible("contact_phone") ? [{ key: "contact_phone", label: "전화번호", width: "w-[120px]" }] : []),
...(isColumnVisible("email") ? [{ key: "email", label: "이메일", width: "w-[160px]" }] : []),
...(isColumnVisible("business_number") ? [{ key: "business_number", label: "사업자번호", width: "w-[120px]" }] : []),
...(isColumnVisible("address") ? [{ key: "address", label: "주소", minWidth: "min-w-[150px]" }] : []),
...(isColumnVisible("status") ? [{
key: "status",
label: "상태",
width: "w-[70px]",
render: (val: any) =>
val ? (
<Badge
variant={val === "활성" || val === "거래중" || val === "정상" ? "default" as const : "outline" as const}
className="text-[10px] px-1.5 py-0 h-5"
>
{val}
</Badge>
) : null,
}] : []),
];
// EDataTable 컬럼 정의 (거래처 목록) — ts.visibleColumns 순서를 따름
const customerColumns: EDataTableColumn[] = useMemo(() => {
const colProps: Record<string, Partial<EDataTableColumn>> = {
customer_code: { width: "w-[120px]" },
customer_name: { minWidth: "min-w-[140px]" },
division: {
width: "w-[80px]",
render: (val: any) =>
val ? (
<Badge variant="secondary" className="text-[10px] px-1.5 py-0 h-5 font-normal">
{val}
</Badge>
) : null,
},
contact_person: { width: "w-[80px]" },
contact_phone: { width: "w-[120px]" },
email: { width: "w-[160px]" },
business_number: { width: "w-[120px]" },
address: { minWidth: "min-w-[150px]" },
status: {
width: "w-[70px]",
render: (val: any) =>
val ? (
<Badge
variant={val === "활성" || val === "거래중" || val === "정상" ? "default" as const : "outline" as const}
className="text-[10px] px-1.5 py-0 h-5"
>
{val}
</Badge>
) : null,
},
};
return ts.visibleColumns.map((col) => ({
key: col.key,
label: col.label,
...colProps[col.key],
}));
}, [ts.visibleColumns]);
// 엑셀 다운로드
const handleExcelDownload = async () => {