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