하위메뉴 추가및 고객사 메뉴적용용
This commit is contained in:
parent
8fca4f8357
commit
4aa0090068
@ -87,7 +87,7 @@ router.get("/codes", async (req, res, next) => {
|
||||
});
|
||||
|
||||
// 공통코드 생성/수정
|
||||
router.post("/codes", async (req, res, next) => { // URL 경로 변경 "/code" -> "/codes"
|
||||
router.post("/code", async (req, res, next) => { // URL 경로 수정
|
||||
try {
|
||||
const codeData = req.body;
|
||||
console.log('Received code data:', codeData);
|
||||
@ -95,7 +95,7 @@ router.post("/codes", async (req, res, next) => { // URL 경로 변경 "/code"
|
||||
res.json({
|
||||
success: true,
|
||||
data: result
|
||||
}); // 응답 형식 통일
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Save code error:', error);
|
||||
next(error);
|
||||
|
@ -153,17 +153,13 @@ const saveCode = async (codeData) => {
|
||||
};
|
||||
|
||||
let result;
|
||||
// ID가 있으면 수정, 없으면 생성
|
||||
if (codeData.id) {
|
||||
// 수정
|
||||
result = await CommCode.update(codeFields, {
|
||||
where: { id: codeData.id },
|
||||
returning: true, // 업데이트된 데이터 반환
|
||||
await CommCode.update(codeFields, {
|
||||
where: { id: codeData.id }
|
||||
});
|
||||
|
||||
const updatedCode = await CommCode.findByPk(codeData.id);
|
||||
result = updatedCode;
|
||||
result = await CommCode.findByPk(codeData.id);
|
||||
} else {
|
||||
// 생성
|
||||
result = await CommCode.create(codeFields);
|
||||
}
|
||||
|
||||
|
@ -69,6 +69,7 @@ interface CodeFormDialogProps {
|
||||
codeData?: CodeFormData;
|
||||
onSave: (data: CodeFormData) => void;
|
||||
codes: CommonCode[];
|
||||
title: string; // title prop 추가
|
||||
}
|
||||
|
||||
// CodeFormDialog 컴포넌트 수정
|
||||
@ -77,7 +78,8 @@ const CodeFormDialog: React.FC<CodeFormDialogProps> = ({
|
||||
onClose,
|
||||
codeData,
|
||||
onSave,
|
||||
codes
|
||||
codes,
|
||||
title, // title prop 사용
|
||||
}) => {
|
||||
const [searchText, setSearchText] = useState("");
|
||||
|
||||
@ -125,9 +127,7 @@ const CodeFormDialog: React.FC<CodeFormDialogProps> = ({
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="sm:max-w-[600px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{codeData ? "공통 코드 수정" : "공통 코드 등록"}
|
||||
</DialogTitle>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSave)} className="space-y-4">
|
||||
@ -249,6 +249,7 @@ const CommonCodePage = () => {
|
||||
const [codeToDelete, setCodeToDelete] = useState<string | null>(null);
|
||||
const [selectedRows, setSelectedRows] = useState<CommonCode[]>([]);
|
||||
const [currentPageSize, setCurrentPageSize] = useState(20); // 페이지 크기 상태 추가
|
||||
const [parentCode, setParentCode] = useState<CommonCode | null>(null); // 상위 코드 상태 추가
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const { toast } = useToast();
|
||||
@ -306,8 +307,22 @@ const CommonCodePage = () => {
|
||||
|
||||
// handleSave 함수 수정
|
||||
const handleSave = (data: CodeFormData) => {
|
||||
console.log('Form data submitted:', data); // 디버깅용
|
||||
saveCodeMutation.mutate(data);
|
||||
// 하위 코드 추가인 경우
|
||||
const saveData = { ...data };
|
||||
|
||||
if (parentCode) {
|
||||
saveData.parent_code_id = parentCode.id;
|
||||
} else if (saveData.parent_code_id === 'root') {
|
||||
saveData.parent_code_id = null;
|
||||
}
|
||||
|
||||
// ID 제거 (새로운 항목 추가 시)
|
||||
if (parentCode || !selectedCode) {
|
||||
delete saveData.id;
|
||||
}
|
||||
|
||||
console.log('Saving code data:', saveData);
|
||||
saveCodeMutation.mutate(saveData);
|
||||
};
|
||||
|
||||
const deleteCodeMutation = useMutation({
|
||||
@ -430,6 +445,29 @@ const CommonCodePage = () => {
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "addSub",
|
||||
header: "하위요소",
|
||||
meta: { width: "100px", textAlign: "center" },
|
||||
cell: ({ row }) => (
|
||||
<div className="text-center">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-8 px-2 text-xs font-medium text-blue-600 hover:text-blue-700 hover:bg-blue-50 border-blue-200"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation(); // 이벤트 전파 중지
|
||||
// 상위 코드 설정 및 빈 코드 데이터로 다이얼로그 열기
|
||||
setSelectedCode(null);
|
||||
setParentCode(row.original);
|
||||
setIsOpen(true);
|
||||
}}
|
||||
>
|
||||
하위추가
|
||||
</Button>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
header: "삭제",
|
||||
@ -512,6 +550,7 @@ const CommonCodePage = () => {
|
||||
}}
|
||||
enableCheckbox={true} // 체크박스 활성화
|
||||
onCheckedChange={setSelectedRows} // 체크된 행들 처리
|
||||
isTreeTable={true} // 트리 테이블 활성화
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -522,8 +561,25 @@ const CommonCodePage = () => {
|
||||
onClose={() => {
|
||||
setIsOpen(false);
|
||||
setSelectedCode(null);
|
||||
setParentCode(null);
|
||||
}}
|
||||
codeData={selectedCode ? { ...selectedCode, description: selectedCode.description || "" } : undefined}
|
||||
codeData={
|
||||
parentCode
|
||||
? {
|
||||
code_cd: "",
|
||||
code_name: "",
|
||||
description: "",
|
||||
parent_code_id: parentCode.id,
|
||||
isActive: true,
|
||||
ext_val: "",
|
||||
order_num: 0
|
||||
}
|
||||
: selectedCode
|
||||
? { ...selectedCode, description: selectedCode.description || "" }
|
||||
: undefined
|
||||
}
|
||||
// DialogTitle 부분 수정
|
||||
title={parentCode ? '하위 코드 추가' : (selectedCode ? '코드 수정' : '새 코드 추가')}
|
||||
onSave={handleSave}
|
||||
codes={codes || []}
|
||||
/>
|
||||
|
@ -77,6 +77,7 @@ interface MenuFormDialogProps {
|
||||
menuData?: DBMenuItem;
|
||||
onSave: (menu: DBMenuItem) => void;
|
||||
menus: DBMenuItem[];
|
||||
title: string; // 추가
|
||||
}
|
||||
|
||||
const getMenuList = (menus: DBMenuItem[], depth = 0): { id: string; label: string; isChild: boolean }[] => {
|
||||
@ -96,6 +97,7 @@ const MenuFormDialog: React.FC<MenuFormDialogProps> = ({
|
||||
menuData,
|
||||
onSave,
|
||||
menus,
|
||||
title, // 추가
|
||||
}) => {
|
||||
const [searchText, setSearchText] = useState("");
|
||||
|
||||
@ -147,7 +149,7 @@ const MenuFormDialog: React.FC<MenuFormDialogProps> = ({
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="sm:max-w-[600px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{menuData ? '메뉴 수정' : '새 메뉴 추가'}</DialogTitle>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSave)} className="space-y-4">
|
||||
@ -290,7 +292,9 @@ const MenuFormDialog: React.FC<MenuFormDialogProps> = ({
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="outline" onClick={onClose}>취소</Button>
|
||||
<Button type="submit">{menuData ? '수정' : '등록'}</Button>
|
||||
<Button type="submit">
|
||||
{menuData ? '수정' : '등록'} {/* parentMenu 대신 codeData 사용 */}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
@ -307,6 +311,7 @@ const MenusPage = () => {
|
||||
const [menuToDelete, setMenuToDelete] = useState<string | null>(null);
|
||||
const [selectedRows, setSelectedRows] = useState<DBMenuItem[]>([]);
|
||||
const [currentPageSize, setCurrentPageSize] = useState(20); // 페이지 크기 상태 추가
|
||||
const [parentMenu, setParentMenu] = useState<DBMenuItem | null>(null); // 상위 메뉴 상태 추가
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const { toast } = useToast();
|
||||
@ -389,6 +394,29 @@ const MenusPage = () => {
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "addSub",
|
||||
header: "하위요소",
|
||||
meta: { width: "100px", textAlign: "center" },
|
||||
cell: ({ row }) => (
|
||||
<div className="text-center">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-8 px-2 text-xs font-medium text-blue-600 hover:text-blue-700 hover:bg-blue-50 border-blue-200"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation(); // 이벤트 전파 중지
|
||||
// 상위 메뉴 설정 및 빈 메뉴 데이터로 다이얼로그 열기
|
||||
setSelectedMenu(null);
|
||||
setParentMenu(row.original);
|
||||
setIsOpen(true);
|
||||
}}
|
||||
>
|
||||
하위추가
|
||||
</Button>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "isActive",
|
||||
accessorKey: "isActive",
|
||||
@ -506,12 +534,17 @@ const MenusPage = () => {
|
||||
|
||||
const handleMenuSave = (menu: DBMenuItem) => {
|
||||
const { children, ...saveData } = menu;
|
||||
// parent_id가 'root'인 경우 null로 변환
|
||||
const finalData = {
|
||||
...saveData,
|
||||
parent_id: saveData.parent_id === 'root' ? null : saveData.parent_id
|
||||
};
|
||||
saveMenuMutation.mutate(finalData);
|
||||
|
||||
// 하위 메뉴 추가인 경우
|
||||
if (parentMenu) {
|
||||
saveData.parent_id = parentMenu.id;
|
||||
saveData.menu_type = parentMenu.menu_type; // 상위 메뉴의 타입을 따름
|
||||
} else if (saveData.parent_id === 'root') {
|
||||
saveData.parent_id = null;
|
||||
}
|
||||
|
||||
console.log('Saving menu data:', saveData); // 디버깅용
|
||||
saveMenuMutation.mutate(saveData);
|
||||
};
|
||||
|
||||
const handleDeleteClick = (id: string) => {
|
||||
@ -525,14 +558,39 @@ const MenusPage = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 검색 로직 수정
|
||||
const filteredMenus = useMemo(() => {
|
||||
if (!menus || !searchQuery) return menus || [];
|
||||
|
||||
const searchLower = searchQuery.toLowerCase();
|
||||
return menus.filter(menu =>
|
||||
(menu.menu_name_kor?.toLowerCase() || '').includes(searchLower) ||
|
||||
(menu.menu_url?.toLowerCase() || '').includes(searchLower)
|
||||
);
|
||||
|
||||
// 재귀적으로 메뉴와 그 자식들을 검색하는 함수
|
||||
const filterMenusRecursive = (items: DBMenuItem[]): DBMenuItem[] => {
|
||||
return items.reduce<DBMenuItem[]>((acc, menu) => {
|
||||
const matchesSearch =
|
||||
(menu.menu_name_kor?.toLowerCase() || '').includes(searchLower) ||
|
||||
(menu.menu_name_eng?.toLowerCase() || '').includes(searchLower) ||
|
||||
(menu.menu_url?.toLowerCase() || '').includes(searchLower);
|
||||
|
||||
if (matchesSearch) {
|
||||
// 검색어와 일치하는 경우 해당 메뉴 포함
|
||||
acc.push(menu);
|
||||
} else if (menu.children && menu.children.length > 0) {
|
||||
// 자식 메뉴들을 검색
|
||||
const filteredChildren = filterMenusRecursive(menu.children);
|
||||
if (filteredChildren.length > 0) {
|
||||
// 자식 중 검색어와 일치하는 것이 있으면 부모도 포함
|
||||
acc.push({
|
||||
...menu,
|
||||
children: filteredChildren
|
||||
});
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
};
|
||||
|
||||
return filterMenusRecursive(menus);
|
||||
}, [menus, searchQuery]);
|
||||
|
||||
if (isLoading) {
|
||||
@ -584,14 +642,38 @@ const MenusPage = () => {
|
||||
}}
|
||||
enableCheckbox={true} // 체크박스 활성화
|
||||
onCheckedChange={setSelectedRows} // 체크된 행들 처리
|
||||
/> </div>
|
||||
isTreeTable={true} // 트리 테이블 활성화
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<MenuFormDialog
|
||||
isOpen={isOpen}
|
||||
onClose={() => setIsOpen(false)}
|
||||
menuData={selectedMenu || undefined}
|
||||
onClose={() => {
|
||||
setIsOpen(false);
|
||||
setSelectedMenu(null);
|
||||
setParentMenu(null);
|
||||
}}
|
||||
menuData={
|
||||
parentMenu
|
||||
? {
|
||||
id: '',
|
||||
menu_type: parentMenu.menu_type,
|
||||
parent_id: parentMenu.id,
|
||||
menu_name_kor: '',
|
||||
menu_name_eng: '',
|
||||
menu_url: '',
|
||||
seq: '0',
|
||||
isActive: true
|
||||
}
|
||||
: selectedMenu || undefined
|
||||
}
|
||||
title={
|
||||
parentMenu
|
||||
? "하위 메뉴 추가"
|
||||
: (selectedMenu ? "메뉴 수정" : "새 메뉴 추가")
|
||||
}
|
||||
onSave={handleMenuSave}
|
||||
menus={menus || []}
|
||||
/>
|
||||
|
@ -1,7 +1,7 @@
|
||||
// src/app/(admin)/users/accounts/page.tsx
|
||||
"use client";
|
||||
|
||||
import React, { useState, useCallback } from "react";
|
||||
import React, { useState, useCallback, useMemo } from "react";
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { DataTable } from "@/components/ui/data-table";
|
||||
@ -15,13 +15,15 @@ import {
|
||||
DialogDescription,
|
||||
} from "@/components/ui/dialog";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { Plus, Edit, Trash2 } from "lucide-react";
|
||||
import { Plus, Edit, Trash2, Search } from "lucide-react";
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { AxiosError } from "axios";
|
||||
import { OemMngForm } from "./components/OemMngForm";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { oemMng, PaginatedResponse } from "@/types/common/oemmng/oemmng";
|
||||
import { PLMTable } from "@/components/common/Table";
|
||||
import { Input } from "@/components/ui/input";
|
||||
|
||||
const OemMngPage = () => {
|
||||
const { token, user } = useAuthStore();
|
||||
@ -31,13 +33,14 @@ const OemMngPage = () => {
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
const { toast } = useToast();
|
||||
const queryClient = useQueryClient();
|
||||
const [currentPageSize, setCurrentPageSize] = useState(20); // 페이지 크기 상태 추가
|
||||
const [searchQuery, setSearchQuery] = useState(""); // 검색어 상태 추가
|
||||
|
||||
// Fetch OEMs with pagination
|
||||
const { data, isLoading } = useQuery<PaginatedResponse>({
|
||||
queryKey: ["oemMngs", page, pageSize],
|
||||
queryFn: async () => {
|
||||
const { data } = await api.get("/api/v1/app/oemmng/oemmngList", {
|
||||
|
||||
params: {
|
||||
page,
|
||||
limit: pageSize,
|
||||
@ -53,12 +56,21 @@ const OemMngPage = () => {
|
||||
enabled: !!token,
|
||||
});
|
||||
|
||||
|
||||
const handlePageSizeChange = useCallback((newPageSize: number) => {
|
||||
setPageSize(newPageSize);
|
||||
setPage(1);
|
||||
}, []);
|
||||
|
||||
// 필터링된 데이터 생성
|
||||
const filteredOemMngs = useMemo(() => {
|
||||
if (!data?.oemMngs || !searchQuery) return data?.oemMngs;
|
||||
|
||||
return data.oemMngs.filter(oem =>
|
||||
oem.oem_code?.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
oem.oem_name?.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
}, [data?.oemMngs, searchQuery]);
|
||||
|
||||
// Create user mutation
|
||||
const createMutation = useMutation({
|
||||
mutationFn: async (newOem: Partial<oemMng>) => {
|
||||
@ -96,7 +108,6 @@ const OemMngPage = () => {
|
||||
mutationFn: async (oemData: Partial<oemMng>) => {
|
||||
const { data } = await api.put<oemMng>(
|
||||
`/api/v1/app/oemmng/oemmngUpdate/${oemData.id}`,
|
||||
|
||||
oemData
|
||||
);
|
||||
return data;
|
||||
@ -140,44 +151,60 @@ const OemMngPage = () => {
|
||||
},
|
||||
});
|
||||
|
||||
// Table columns
|
||||
// Table columns 수정
|
||||
const columns: ColumnDef<oemMng>[] = [
|
||||
{
|
||||
id: "index",
|
||||
header: "No",
|
||||
cell: ({ row }) => row.original.index,
|
||||
size: 60,
|
||||
meta: {
|
||||
width: "60px",
|
||||
textAlign: "center",
|
||||
},
|
||||
},
|
||||
// {
|
||||
// accessorKey: "username",
|
||||
// header: "아이디",
|
||||
// },
|
||||
{
|
||||
accessorKey: "oem_code",
|
||||
header: "업체명/고객사",
|
||||
size: 120,
|
||||
meta: {
|
||||
width: "120px",
|
||||
textAlign: "center",
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "oem_name",
|
||||
header: "OEM 이름",
|
||||
meta: {
|
||||
width: "200px",
|
||||
textAlign: "center",
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "isActive",
|
||||
header: "활성화",
|
||||
meta: {
|
||||
width: "100px",
|
||||
textAlign: "center",
|
||||
},
|
||||
cell: ({ row }) => (
|
||||
<Switch
|
||||
checked={row.original.isActive}
|
||||
onCheckedChange={(value) => {
|
||||
updateMutation.mutate({ id: row.original.id, isActive: value });
|
||||
}}
|
||||
/>
|
||||
<div className="text-center">
|
||||
<Switch
|
||||
checked={row.original.isActive}
|
||||
onCheckedChange={(value) => {
|
||||
updateMutation.mutate({ id: row.original.id, isActive: value });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
header: "액션",
|
||||
meta: {
|
||||
width: "100px",
|
||||
textAlign: "center",
|
||||
},
|
||||
cell: ({ row }) => (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
@ -207,52 +234,57 @@ const OemMngPage = () => {
|
||||
if (isLoading) return <div>Loading...</div>;
|
||||
|
||||
return (
|
||||
<div className="container mx-auto py-6">
|
||||
{/* Header */}
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<div className="space-y-1">
|
||||
<h1 className="text-3xl font-bold">고객사 관리</h1>
|
||||
<p className="text-muted-foreground">
|
||||
고객사를 관리하고 권한을 설정합니다.
|
||||
</p>
|
||||
</div>
|
||||
<Button onClick={() => setIsOpen(true)}>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
고객사 추가
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* OEMs Table */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>고객사 목록</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{data?.oemMngs && data.oemMngs.length > 0 ? (
|
||||
<>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={data.oemMngs}
|
||||
pagination={{
|
||||
pageIndex: page - 1,
|
||||
pageSize,
|
||||
pageCount: data.totalPages,
|
||||
rowCount: data.total,
|
||||
onPageChange: (newPage) => setPage(newPage + 1),
|
||||
onPageSizeChange: handlePageSizeChange,
|
||||
}}
|
||||
/>
|
||||
<div className="text-sm text-muted-foreground mt-2">
|
||||
총 {data.total}명의 고객사
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="text-center py-12 text-muted-foreground">
|
||||
등록된 고객사가 없습니다.
|
||||
<div className="container mx-auto py-4">
|
||||
<div className="bg-white rounded-lg shadow">
|
||||
<div className="p-4 border-b border-gray-200">
|
||||
<div className="flex justify-between items-center mb-3">
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold text-gray-900">고객사 관리</h1>
|
||||
<p className="text-sm text-gray-500">
|
||||
고객사를 관리하고 권한을 설정합니다.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Button onClick={() => setIsOpen(true)}>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
고객사 추가
|
||||
</Button>
|
||||
</div>
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2 top-2.5 h-4 w-4 text-gray-400" />
|
||||
<Input
|
||||
placeholder="고객사명 또는 업체명 검색"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-8 w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4">
|
||||
<div className="h-[650px]">
|
||||
{data?.oemMngs && data.oemMngs.length > 0 ? (
|
||||
<>
|
||||
<PLMTable
|
||||
columns={columns}
|
||||
data={filteredOemMngs || []} // 필터링된 데이터 사용
|
||||
pageSize={currentPageSize}
|
||||
onPageSizeChange={setCurrentPageSize}
|
||||
onRowClick={(row: oemMng) => {
|
||||
setEditingUser(row);
|
||||
setIsOpen(true);
|
||||
}}
|
||||
enableCheckbox={true}
|
||||
isTreeTable={false}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<div className="text-center py-12 text-muted-foreground">
|
||||
등록된 고객사가 없습니다.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* User Create/Edit Dialog */}
|
||||
<Dialog
|
||||
|
@ -43,6 +43,7 @@ interface PLMTableProps<T extends BaseRow> {
|
||||
enableCheckbox?: boolean;
|
||||
onCheckedChange?: (checkedRows: T[]) => void;
|
||||
onPageSizeChange?: (newPageSize: number) => void; // 새로운 prop 추가
|
||||
isTreeTable?: boolean; // 트리 테이블 여부를 결정하는 prop 추가
|
||||
}
|
||||
|
||||
type CustomExpandedState = Record<string | number, boolean>;
|
||||
@ -55,6 +56,7 @@ export function PLMTable<T extends BaseRow>({
|
||||
enableCheckbox = false,
|
||||
onCheckedChange,
|
||||
onPageSizeChange,
|
||||
isTreeTable = false, // 기본값은 false
|
||||
}: PLMTableProps<T>) {
|
||||
const [expanded, setExpanded] = useState<CustomExpandedState>({});
|
||||
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({});
|
||||
@ -204,39 +206,41 @@ export function PLMTable<T extends BaseRow>({
|
||||
});
|
||||
}
|
||||
|
||||
// 확장/축소 버튼 컬럼
|
||||
result.push({
|
||||
id: 'expander',
|
||||
meta: { width: "40px" },
|
||||
header: () => null,
|
||||
cell: ({ row }) => (
|
||||
<div className="flex justify-center">
|
||||
{row.getCanExpand() && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-8 w-8 p-0"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
row.toggleExpanded();
|
||||
}}
|
||||
>
|
||||
{row.getIsExpanded() ? (
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
});
|
||||
// 확장/축소 버튼 컬럼 - 트리 테이블인 경우에만 추가
|
||||
if (isTreeTable) {
|
||||
result.push({
|
||||
id: 'expander',
|
||||
meta: { width: "40px" },
|
||||
header: () => null,
|
||||
cell: ({ row }) => (
|
||||
<div className="flex justify-center">
|
||||
{row.getCanExpand() && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-8 w-8 p-0"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
row.toggleExpanded();
|
||||
}}
|
||||
>
|
||||
{row.getIsExpanded() ? (
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
// 나머지 컬럼들 추가
|
||||
result.push(...userColumns);
|
||||
|
||||
return result;
|
||||
}, [userColumns, enableCheckbox, onCheckedChange]);
|
||||
}, [userColumns, enableCheckbox, onCheckedChange, isTreeTable]);
|
||||
|
||||
const handleExpandedChange: OnChangeFn<ExpandedState> = useCallback((updater) => {
|
||||
setExpanded(old => {
|
||||
|
Loading…
Reference in New Issue
Block a user