충돌해결 및 변경사항 커밋
This commit is contained in:
parent
74f6c9ba83
commit
5e778d3052
@ -21,6 +21,7 @@ const oemMngController = require("../controllers/app/common/oemmng.controller");
|
||||
const productgroupController = require("../controllers/app/common/productgroup.controller");
|
||||
const productController = require("../controllers/app/common/product.controller");
|
||||
const carMngController = require("../controllers/app/common/carmng.controller");
|
||||
const contractController = require("../controllers/app/contract/contract.controller");
|
||||
|
||||
router.use("/health", healthController);
|
||||
router.use("/auth", authController);
|
||||
@ -41,5 +42,6 @@ router.use("/oemmng", oemMngController);
|
||||
router.use("/productgroup", productgroupController);
|
||||
router.use("/product", productController);
|
||||
router.use("/carmng", carMngController);
|
||||
router.use("/contract", contractController);
|
||||
|
||||
module.exports = router;
|
||||
|
@ -1,5 +1,5 @@
|
||||
// src/services/contract.service.js
|
||||
const { Contract, ContractDetail, Company } = require("../models");
|
||||
const { Contract, ContractDetail, Company, ContractMgmt} = require("../models");
|
||||
const alertService = require("./alert.service");
|
||||
const { Op } = require("sequelize");
|
||||
// const { getKoreanSubjectParticle } = require("../utils/koreanParticle");
|
||||
@ -114,6 +114,118 @@ class ContractService {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// 영업정보 목록 조회 수정
|
||||
async getContractList(page = 1, limit = 20) {
|
||||
try {
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
const { count, rows } = await ContractMgmt.findAndCountAll({
|
||||
attributes: {
|
||||
exclude: ['createdAt', 'updatedAt', 'created_at', 'updated_at'] // 모든 타임스탬프 필드 제외
|
||||
},
|
||||
order: [['created_at', 'DESC']], // created_at으로 수정
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
raw: true
|
||||
});
|
||||
|
||||
if (!rows || !Array.isArray(rows)) {
|
||||
throw new Error('No data found');
|
||||
}
|
||||
|
||||
// 데이터 형식 변환
|
||||
const formatted = rows.map(row => ({
|
||||
...row,
|
||||
// Date 형식 변환 및 검사 추가
|
||||
customer_info: row.customer_info ? new Date(row.customer_info).toISOString() : null,
|
||||
duckil_info: row.duckil_info ? new Date(row.duckil_info).toISOString() : null,
|
||||
register_date: row.register_date ? new Date(row.register_date).toISOString() : null,
|
||||
request_date: row.request_date ? new Date(row.request_date).toISOString() : null,
|
||||
submit_date: row.submit_date ? new Date(row.submit_date).toISOString() : null,
|
||||
created_at: row.created_at ? new Date(row.created_at).toISOString() : null,
|
||||
updated_at: row.updated_at ? new Date(row.updated_at).toISOString() : null,
|
||||
|
||||
// 숫자 데이터 변환
|
||||
progress: row.progress ? parseFloat(row.progress) : null,
|
||||
customer_total: row.customer_total ? parseInt(row.customer_total, 10) : null,
|
||||
duckil_production: row.duckil_production ? parseInt(row.duckil_production, 10) : null,
|
||||
first_quarter: row.first_quarter ? parseInt(row.first_quarter, 10) : null,
|
||||
second_quarter: row.second_quarter ? parseInt(row.second_quarter, 10) : null,
|
||||
third_quarter: row.third_quarter ? parseInt(row.third_quarter, 10) : null,
|
||||
fourth_quarter: row.fourth_quarter ? parseInt(row.fourth_quarter, 10) : null,
|
||||
}));
|
||||
|
||||
return {
|
||||
oemMngs: formatted,
|
||||
total: count,
|
||||
totalPages: Math.ceil(count / limit),
|
||||
currentPage: page,
|
||||
pageSize: limit
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Contract Service Error:', error); // 에러 로깅 추가
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 영업정보 생성 (데이터 타입 변환 추가)
|
||||
async createContractMgmt(contractData) {
|
||||
try {
|
||||
const formattedData = this._formatContractData(contractData);
|
||||
return await ContractMgmt.create(formattedData);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 영업정보 수정 (데이터 타입 변환 추가)
|
||||
async updateContractMgmt(id, updateData) {
|
||||
try {
|
||||
const contract = await ContractMgmt.findByPk(id);
|
||||
if (!contract) throw new Error('Contract not found');
|
||||
|
||||
const formattedData = this._formatContractData(updateData);
|
||||
await contract.update(formattedData);
|
||||
return contract;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 영업정보 삭제
|
||||
async deleteContractMgmt(id) {
|
||||
try {
|
||||
const contract = await ContractMgmt.findByPk(id);
|
||||
if (!contract) throw new Error('Contract not found');
|
||||
|
||||
await contract.destroy();
|
||||
return true;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 데이터 형식 변환 헬퍼 메서드
|
||||
_formatContractData(data) {
|
||||
return {
|
||||
...data,
|
||||
// 날짜 문자열을 Date 객체로 변환
|
||||
customer_info: data.customer_info ? new Date(data.customer_info) : null,
|
||||
duckil_info: data.duckil_info ? new Date(data.duckil_info) : null,
|
||||
register_date: data.register_date ? new Date(data.register_date) : null,
|
||||
request_date: data.request_date ? new Date(data.request_date) : null,
|
||||
submit_date: data.submit_date ? new Date(data.submit_date) : null,
|
||||
// 숫자 문자열을 숫자로 변환
|
||||
progress: data.progress ? Number(data.progress) : null,
|
||||
customer_total: data.customer_total ? Number(data.customer_total) : null,
|
||||
duckil_production: data.duckil_production ? Number(data.duckil_production) : null,
|
||||
first_quarter: data.first_quarter ? Number(data.first_quarter) : null,
|
||||
second_quarter: data.second_quarter ? Number(data.second_quarter) : null,
|
||||
third_quarter: data.third_quarter ? Number(data.third_quarter) : null,
|
||||
fourth_quarter: data.fourth_quarter ? Number(data.fourth_quarter) : null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new ContractService();
|
||||
|
@ -422,29 +422,6 @@ const CommonCodePage = () => {
|
||||
<div>{new Date(row.original.createdAt).toLocaleDateString()}</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "isActive",
|
||||
accessorKey: "isActive",
|
||||
header: "활성화",
|
||||
meta: {
|
||||
width: "200px",
|
||||
textAlign: "center"
|
||||
},
|
||||
cell: ({ row }) => (
|
||||
<div className="text-center">
|
||||
<Switch
|
||||
checked={row.original.isActive}
|
||||
onCheckedChange={(checked) =>
|
||||
toggleActiveMutation.mutate({
|
||||
id: row.original.id,
|
||||
isActive: checked
|
||||
})
|
||||
}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "addSub",
|
||||
header: "하위요소",
|
||||
@ -468,6 +445,29 @@ const CommonCodePage = () => {
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "isActive",
|
||||
accessorKey: "isActive",
|
||||
header: "활성화",
|
||||
meta: {
|
||||
width: "200px",
|
||||
textAlign: "center"
|
||||
},
|
||||
cell: ({ row }) => (
|
||||
<div className="text-center">
|
||||
<Switch
|
||||
checked={row.original.isActive}
|
||||
onCheckedChange={(checked) =>
|
||||
toggleActiveMutation.mutate({
|
||||
id: row.original.id,
|
||||
isActive: checked
|
||||
})
|
||||
}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
header: "삭제",
|
||||
|
@ -373,27 +373,6 @@ const MenusPage = () => {
|
||||
textAlign: "center"
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "menu_type",
|
||||
accessorKey: "menu_type",
|
||||
header: "타입",
|
||||
meta: {
|
||||
width: "200px",
|
||||
textAlign: "center"
|
||||
},
|
||||
cell: ({ row }) => (
|
||||
<div className="text-center">
|
||||
<span className={cn(
|
||||
"inline-flex items-center rounded-md px-2 py-1 text-xs font-medium ring-1 ring-inset",
|
||||
row.original.menu_type === "0"
|
||||
? "bg-blue-50 text-blue-700 ring-blue-700/10"
|
||||
: "bg-green-50 text-green-700 ring-green-600/20"
|
||||
)}>
|
||||
{row.original.menu_type === "0" ? "관리자 메뉴" : "사용자 메뉴"}
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "addSub",
|
||||
header: "하위요소",
|
||||
@ -417,6 +396,27 @@ const MenusPage = () => {
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "menu_type",
|
||||
accessorKey: "menu_type",
|
||||
header: "타입",
|
||||
meta: {
|
||||
width: "200px",
|
||||
textAlign: "center"
|
||||
},
|
||||
cell: ({ row }) => (
|
||||
<div className="text-center">
|
||||
<span className={cn(
|
||||
"inline-flex items-center rounded-md px-2 py-1 text-xs font-medium ring-1 ring-inset",
|
||||
row.original.menu_type === "0"
|
||||
? "bg-blue-50 text-blue-700 ring-blue-700/10"
|
||||
: "bg-green-50 text-green-700 ring-green-600/20"
|
||||
)}>
|
||||
{row.original.menu_type === "0" ? "관리자 메뉴" : "사용자 메뉴"}
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "isActive",
|
||||
accessorKey: "isActive",
|
||||
|
@ -13,6 +13,8 @@ import {
|
||||
type OnChangeFn,
|
||||
type Row,
|
||||
type TableOptions,
|
||||
type GroupingColumnDef,
|
||||
type Cell,
|
||||
} from "@tanstack/react-table";
|
||||
import { ChevronRight, ChevronDown } from "lucide-react";
|
||||
|
||||
@ -44,10 +46,79 @@ interface PLMTableProps<T extends BaseRow> {
|
||||
onCheckedChange?: (checkedRows: T[]) => void;
|
||||
onPageSizeChange?: (newPageSize: number) => void; // 새로운 prop 추가
|
||||
isTreeTable?: boolean; // 트리 테이블 여부를 결정하는 prop 추가
|
||||
renderExpandedRow?: (row: Row<T>) => React.ReactNode; // 확장된 행 렌더링을 위한 prop
|
||||
enableExpander?: boolean; // 확장 기능 활성화 여부
|
||||
treeConfig?: { // 트리 구조 설정
|
||||
getParentId?: (row: T) => string | null;
|
||||
indent?: number;
|
||||
};
|
||||
expanderConfig?: { // 행 확장 설정
|
||||
renderExpandedContent?: (row: Row<T>) => React.ReactNode;
|
||||
expandedContentStyle?: React.CSSProperties;
|
||||
};
|
||||
onCellEdit?: (rowId: string, field: string, value: any) => Promise<void>;
|
||||
editableColumns?: string[]; // 수정 가능한 컬럼 목록
|
||||
}
|
||||
|
||||
type CustomExpandedState = Record<string | number, boolean>;
|
||||
|
||||
// EditableCell 컴포넌트 추가
|
||||
const EditableCell = <T extends BaseRow>({
|
||||
cell,
|
||||
isEditable,
|
||||
onCellEdit,
|
||||
}: {
|
||||
cell: Cell<T, unknown>;
|
||||
isEditable?: boolean;
|
||||
onCellEdit?: (rowId: string, field: string, value: any) => Promise<void>;
|
||||
}) => {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [editValue, setEditValue] = useState<string>('');
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const handleDoubleClick = () => {
|
||||
if (!isEditable) return;
|
||||
setIsEditing(true);
|
||||
setEditValue(String(cell.getValue() || ''));
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
if (onCellEdit) {
|
||||
try {
|
||||
await onCellEdit(cell.row.original.id, String(cell.column.id), editValue);
|
||||
setIsEditing(false);
|
||||
} catch (error) {
|
||||
console.error('Failed to update cell:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isEditing && inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
}
|
||||
}, [isEditing]);
|
||||
|
||||
if (isEditing) {
|
||||
return (
|
||||
<input
|
||||
ref={inputRef}
|
||||
className="w-full px-2 py-1 border rounded"
|
||||
value={editValue}
|
||||
onChange={(e) => setEditValue(e.target.value)}
|
||||
onBlur={handleSave}
|
||||
onKeyPress={(e) => e.key === 'Enter' && handleSave()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div onDoubleClick={handleDoubleClick}>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export function PLMTable<T extends BaseRow>({
|
||||
columns: userColumns,
|
||||
data,
|
||||
@ -57,6 +128,12 @@ export function PLMTable<T extends BaseRow>({
|
||||
onCheckedChange,
|
||||
onPageSizeChange,
|
||||
isTreeTable = false, // 기본값은 false
|
||||
renderExpandedRow,
|
||||
enableExpander = false,
|
||||
treeConfig,
|
||||
expanderConfig,
|
||||
onCellEdit,
|
||||
editableColumns,
|
||||
}: PLMTableProps<T>) {
|
||||
const [expanded, setExpanded] = useState<CustomExpandedState>({});
|
||||
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({});
|
||||
@ -218,7 +295,7 @@ export function PLMTable<T extends BaseRow>({
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-8 w-8 p-0"
|
||||
className={`h-8 w-8 p-0 ${row.depth > 0 ? 'ml-4' : ''}`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
row.toggleExpanded();
|
||||
@ -236,11 +313,39 @@ export function PLMTable<T extends BaseRow>({
|
||||
});
|
||||
}
|
||||
|
||||
// 확장/축소 버튼 컬럼 - enableExpander가 true일 때만 추가
|
||||
if (enableExpander) {
|
||||
result.push({
|
||||
id: 'expander',
|
||||
meta: { width: "40px" },
|
||||
header: () => null,
|
||||
cell: ({ row }) => (
|
||||
<div className="flex justify-center">
|
||||
<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, isTreeTable]);
|
||||
}, [userColumns, enableCheckbox, onCheckedChange, isTreeTable, enableExpander]);
|
||||
|
||||
const handleExpandedChange: OnChangeFn<ExpandedState> = useCallback((updater) => {
|
||||
setExpanded(old => {
|
||||
@ -307,77 +412,245 @@ export function PLMTable<T extends BaseRow>({
|
||||
onPageSizeChange(newPageSize);
|
||||
}
|
||||
};
|
||||
const getHeaderDepth = (columns: GroupingColumnDef<T, any>[]): number => {
|
||||
let maxDepth = 1;
|
||||
for (const column of columns) {
|
||||
if ('columns' in column && Array.isArray(column.columns)) {
|
||||
const subDepth = getHeaderDepth(column.columns as GroupingColumnDef<T, any>[]) + 1;
|
||||
maxDepth = Math.max(maxDepth, subDepth);
|
||||
}
|
||||
}
|
||||
return maxDepth;
|
||||
};
|
||||
|
||||
const defaultRenderExpandedRow = (row: Row<T>) => {
|
||||
const item = row.original;
|
||||
return (
|
||||
<div className="pl-12 pr-4 py-2 bg-gray-50">
|
||||
<table className="min-w-full border-separate border-spacing-0 text-xs">
|
||||
{Object.entries(item).map(([key, value]) => {
|
||||
// id나 기본 표시되는 컬럼들은 제외
|
||||
if (key === 'id' || columns.some(col => 'accessorKey' in col && col.accessorKey === key)) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<tr key={key}>
|
||||
<th className="w-48 py-1 px-2 text-left font-medium text-gray-700">{key}</th>
|
||||
<td className="py-1 px-2">{String(value)}</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// 트리 구조 관련 로직
|
||||
const renderTreeCell = useCallback((row: Row<T>) => {
|
||||
if (!isTreeTable || !treeConfig) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative"
|
||||
style={{ paddingLeft: `${(row.depth || 0) * (treeConfig.indent || 24)}px` }}
|
||||
>
|
||||
{/* 트리 라인 렌더링 */}
|
||||
</div>
|
||||
);
|
||||
}, [isTreeTable, treeConfig]);
|
||||
|
||||
// 행 확장 관련 로직
|
||||
const renderExpandedRowContent = useCallback((row: Row<T>) => {
|
||||
if (!enableExpander || !expanderConfig?.renderExpandedContent) return null;
|
||||
|
||||
return (
|
||||
<div style={expanderConfig.expandedContentStyle}>
|
||||
{expanderConfig.renderExpandedContent(row)}
|
||||
</div>
|
||||
);
|
||||
}, [enableExpander, expanderConfig]);
|
||||
|
||||
// renderCell 함수를 컴포넌트로 교체
|
||||
const renderCell = useCallback((cell: Cell<T, unknown>) => {
|
||||
const isEditable = editableColumns?.includes(String(cell.column.id));
|
||||
return (
|
||||
<EditableCell
|
||||
cell={cell}
|
||||
isEditable={isEditable}
|
||||
onCellEdit={onCellEdit}
|
||||
/>
|
||||
);
|
||||
}, [editableColumns, onCellEdit]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
className="relative overflow-y-auto overflow-x-auto bg-white border border-gray-200 rounded-lg shadow-sm"
|
||||
className="relative border border-gray-200 rounded-lg shadow-sm"
|
||||
style={{ height: "600px" }}
|
||||
>
|
||||
<table className="w-full border-separate border-spacing-0 text-sm leading-normal text-gray-700 table-fixed">
|
||||
<thead>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<tr key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => (
|
||||
<th
|
||||
key={header.id}
|
||||
className="sticky top-0 z-10 bg-gray-50 text-center py-3 px-4 font-semibold text-gray-900 border-b border-gray-200 whitespace-nowrap overflow-hidden text-ellipsis"
|
||||
style={{ width: header.column.columnDef.meta?.width }}
|
||||
>
|
||||
{flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</thead>
|
||||
<tbody>
|
||||
{table.getRowModel().rows.map((row) => (
|
||||
<tr
|
||||
key={row.id}
|
||||
className={`transition-colors hover:bg-gray-50`}
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => {
|
||||
const isFirstCell = cell.column.id === 'expander';
|
||||
return (
|
||||
<td
|
||||
key={cell.id}
|
||||
className={`py-3 px-4 border-b border-gray-200 overflow-visible
|
||||
${cell.column.columnDef.meta?.className || ""}
|
||||
${isFirstCell ? 'relative' : ''}`}
|
||||
style={{
|
||||
width: cell.column.columnDef.meta?.width,
|
||||
maxWidth: cell.column.columnDef.meta?.width,
|
||||
textAlign: cell.column.columnDef.meta?.textAlign || 'left',
|
||||
paddingLeft: isFirstCell ? `${row.depth * 24 + 16}px` : undefined,
|
||||
}}
|
||||
>
|
||||
{isFirstCell && Array.from({ length: row.depth }).map((_, index) => (
|
||||
<span
|
||||
key={`line-${index}`}
|
||||
className="absolute h-full w-px bg-gray-200"
|
||||
<div className="absolute top-0 left-0 right-0 bottom-0 overflow-auto">
|
||||
<table className="min-w-full border-separate border-spacing-0 text-[13px]"> {/* text-xs에서 text-[13px]로 변경 */}
|
||||
<thead className="sticky top-0 z-10 bg-gray-100">
|
||||
{table.getHeaderGroups().map((headerGroup, groupIndex) => {
|
||||
// 헤더 그룹의 전체 개수
|
||||
const totalHeaderGroups = table.getHeaderGroups().length;
|
||||
|
||||
return (
|
||||
<tr key={headerGroup.id} className="bg-gray-50">
|
||||
{headerGroup.headers.map((header) => {
|
||||
const isUtilityColumn = header.column.id === 'select' || header.column.id === 'expander';
|
||||
const isNestedHeader = header.column.columns?.length > 0;
|
||||
|
||||
// 유틸리티 컬럼(체크박스, expander)은 첫 번째 행에서만 표시하고 전체 높이로 설정
|
||||
if (groupIndex > 0 && isUtilityColumn) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 부모 컬럼이 없는 일반 컬럼은 첫 번째 행에서만 표시하고 전체 높이로 설정
|
||||
if (groupIndex > 0 && !header.column.parent && !isNestedHeader) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 중첩된 컬럼의 자식은 마지막 행에서만 표시
|
||||
if (groupIndex < totalHeaderGroups - 1 && !isNestedHeader && header.column.parent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// rowSpan 계산
|
||||
let rowSpan = 1;
|
||||
if (isUtilityColumn || (!isNestedHeader && !header.column.parent)) {
|
||||
rowSpan = totalHeaderGroups; // 전체 높이
|
||||
} else if (!isNestedHeader && header.column.parent) {
|
||||
rowSpan = 1; // 자식 컬럼은 한 줄
|
||||
} else if (isNestedHeader) {
|
||||
rowSpan = 1; // 그룹 헤더는 한 줄
|
||||
}
|
||||
|
||||
return (
|
||||
<th
|
||||
key={header.id}
|
||||
colSpan={header.colSpan}
|
||||
rowSpan={rowSpan}
|
||||
className={`
|
||||
sticky top-0 z-10
|
||||
text-center py-0.5 px-2
|
||||
font-semibold text-gray-900
|
||||
whitespace-nowrap overflow-hidden text-ellipsis
|
||||
bg-gray-100
|
||||
${isNestedHeader ? 'font-bold' : ''}
|
||||
last:border-r-0
|
||||
`}
|
||||
style={{
|
||||
left: `${(index + 1) * 24}px`,
|
||||
top: 0,
|
||||
zIndex: 1
|
||||
width: header.column.columnDef.meta?.width,
|
||||
minWidth: header.column.columnDef.meta?.width,
|
||||
height: '24px', // 더 작게 조정
|
||||
borderBottom: '1px solid #D1D5DB',
|
||||
borderRight: '1px solid #D1D5DB',
|
||||
borderTop: groupIndex === 0 ? '1px solid #D1D5DB' : 'none',
|
||||
borderLeft: 'none',
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
<div className={isFirstCell ? 'relative z-10' : ''}>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</div>
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
>
|
||||
{flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
</th>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-50">
|
||||
{table.getRowModel().rows.map((row) => (
|
||||
<React.Fragment key={row.id}>
|
||||
<tr
|
||||
className="transition-colors hover:bg-gray-50"
|
||||
>
|
||||
{row.getVisibleCells().map((cell, cellIndex) => {
|
||||
const isFirstCell = cell.column.id === 'expander';
|
||||
const isSecondCell = cellIndex === 1; // expander 다음 셀 확인
|
||||
const hasExpandButton = row.getCanExpand();
|
||||
|
||||
return (
|
||||
<td
|
||||
key={cell.id}
|
||||
className={`py-0.5 px-3 border-b border-gray-200
|
||||
${cell.column.columnDef.meta?.className || ""}
|
||||
${isFirstCell ? 'relative' : ''}
|
||||
${isSecondCell && hasExpandButton ? 'border-l-0' : ''}
|
||||
${!isFirstCell ? 'border-r border-gray-200' : ''}`}
|
||||
style={{
|
||||
width: cell.column.columnDef.meta?.width,
|
||||
maxWidth: cell.column.columnDef.meta?.width,
|
||||
textAlign: cell.column.columnDef.meta?.textAlign || 'left',
|
||||
paddingLeft: isFirstCell ? `${row.depth * 24 + 12}px` : undefined,
|
||||
}}
|
||||
>
|
||||
{isFirstCell && row.depth > 0 && (
|
||||
<>
|
||||
{/* 수직 라인 */}
|
||||
<span
|
||||
className="absolute h-full w-px bg-gray-200"
|
||||
style={{
|
||||
left: '24px',
|
||||
top: 0,
|
||||
}}
|
||||
/>
|
||||
{/* 수평 라인 */}
|
||||
<span
|
||||
className="absolute w-4 h-px bg-gray-200"
|
||||
style={{
|
||||
left: '24px',
|
||||
top: '50%',
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div className={`relative ${isFirstCell ? 'z-10' : ''}`}>
|
||||
{renderCell(cell)}
|
||||
</div>
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
{/* 확장된 행 렌더링 */}
|
||||
{row.getIsExpanded() && (
|
||||
<tr>
|
||||
<td
|
||||
colSpan={row.getVisibleCells().length}
|
||||
className="border-b border-gray-200 bg-transparent"
|
||||
>
|
||||
<div className="relative">
|
||||
{/* 왼쪽 세로 선 */}
|
||||
<div
|
||||
className="absolute left-0 top-0 bottom-0 w-[1px] bg-gray-200"
|
||||
style={{
|
||||
left: `${row.depth * 24 + 24}px`,
|
||||
}}
|
||||
/>
|
||||
{/* 확장된 내용을 선보다 오른쪽으로 이동 */}
|
||||
<div
|
||||
className="ml-[48px] py-2"
|
||||
style={{
|
||||
width: 'calc(100% - 64px)', // 전체 너비에서 왼쪽 여백(48px)과 오른쪽 여백(16px)을 뺌
|
||||
marginRight: '16px'
|
||||
}}
|
||||
>
|
||||
{renderExpandedRowContent(row)}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between py-3 text-sm text-gray-700">
|
||||
<div className="flex items-center justify-between py-3 text-[13px] text-gray-700"> {/* text-sm에서 text-[13px]로 변경 */}
|
||||
<div className="w-1/3 flex items-center gap-2">
|
||||
<span>페이지당 행:</span>
|
||||
<select
|
||||
|
Loading…
Reference in New Issue
Block a user