summaryrefslogtreecommitdiff
path: root/lib/evaluation-target-list/table/evaluation-target-table.tsx
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-06-20 11:37:31 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-06-20 11:37:31 +0000
commitaa86729f9a2ab95346a2851e3837de1c367aae17 (patch)
treeb601b18b6724f2fb449c7fa9ea50cbd652a8077d /lib/evaluation-target-list/table/evaluation-target-table.tsx
parent95bbe9c583ff841220da1267630e7b2025fc36dc (diff)
(대표님) 20250620 작업사항
Diffstat (limited to 'lib/evaluation-target-list/table/evaluation-target-table.tsx')
-rw-r--r--lib/evaluation-target-list/table/evaluation-target-table.tsx154
1 files changed, 95 insertions, 59 deletions
diff --git a/lib/evaluation-target-list/table/evaluation-target-table.tsx b/lib/evaluation-target-list/table/evaluation-target-table.tsx
index 15837733..fe0b3188 100644
--- a/lib/evaluation-target-list/table/evaluation-target-table.tsx
+++ b/lib/evaluation-target-list/table/evaluation-target-table.tsx
@@ -25,6 +25,7 @@ import { getEvaluationTargetsColumns } from "./evaluation-targets-columns"
import { EvaluationTargetsTableToolbarActions } from "./evaluation-targets-toolbar-actions"
import { EvaluationTargetFilterSheet } from "./evaluation-targets-filter-sheet"
import { EvaluationTargetWithDepartments } from "@/db/schema"
+import { EditEvaluationTargetSheet } from "./update-evaluation-target"
interface EvaluationTargetsTableProps {
promises: Promise<[Awaited<ReturnType<typeof getEvaluationTargets>>]>
@@ -40,13 +41,13 @@ function EvaluationTargetsStats({ evaluationYear }: { evaluationYear: number })
React.useEffect(() => {
let isMounted = true
-
+
async function fetchStats() {
try {
setIsLoading(true)
setError(null)
const statsData = await getEvaluationTargetsStats(evaluationYear)
-
+
if (isMounted) {
setStats(statsData)
}
@@ -186,45 +187,59 @@ function EvaluationTargetsStats({ evaluationYear }: { evaluationYear: number })
export function EvaluationTargetsTable({ promises, evaluationYear, className }: EvaluationTargetsTableProps) {
const [rowAction, setRowAction] = React.useState<DataTableRowAction<EvaluationTargetWithDepartments> | null>(null)
const [isFilterPanelOpen, setIsFilterPanelOpen] = React.useState(false)
- console.count("E Targets render");
const router = useRouter()
const searchParams = useSearchParams()
const containerRef = React.useRef<HTMLDivElement>(null)
const [containerTop, setContainerTop] = React.useState(0)
+ // ✅ 스크롤 이벤트 throttling으로 성능 최적화
const updateContainerBounds = React.useCallback(() => {
if (containerRef.current) {
const rect = containerRef.current.getBoundingClientRect()
- setContainerTop(rect.top)
+ const newTop = rect.top
+
+ // ✅ 값이 실제로 변경될 때만 상태 업데이트
+ setContainerTop(prevTop => {
+ if (Math.abs(prevTop - newTop) > 1) { // 1px 이상 차이날 때만 업데이트
+ return newTop
+ }
+ return prevTop
+ })
}
}, [])
+ // ✅ throttle 함수 추가
+ const throttledUpdateBounds = React.useCallback(() => {
+ let timeoutId: NodeJS.Timeout
+ return () => {
+ clearTimeout(timeoutId)
+ timeoutId = setTimeout(updateContainerBounds, 16) // ~60fps
+ }
+ }, [updateContainerBounds])
+
React.useEffect(() => {
updateContainerBounds()
-
+
+ const throttledHandler = throttledUpdateBounds()
+
const handleResize = () => {
updateContainerBounds()
}
-
+
window.addEventListener('resize', handleResize)
- window.addEventListener('scroll', updateContainerBounds)
-
+ window.addEventListener('scroll', throttledHandler) // ✅ throttled 함수 사용
+
return () => {
window.removeEventListener('resize', handleResize)
- window.removeEventListener('scroll', updateContainerBounds)
+ window.removeEventListener('scroll', throttledHandler)
}
- }, [updateContainerBounds])
+ }, [updateContainerBounds, throttledUpdateBounds])
const [promiseData] = React.use(promises)
const tableData = promiseData
- console.log("Evaluation Targets Table Data:", {
- dataLength: tableData.data?.length,
- pageCount: tableData.pageCount,
- total: tableData.total,
- sampleData: tableData.data?.[0]
- })
+ console.log(tableData)
const initialSettings = React.useMemo(() => ({
page: parseInt(searchParams.get('page') || '1'),
@@ -232,7 +247,7 @@ export function EvaluationTargetsTable({ promises, evaluationYear, className }:
sort: searchParams.get('sort') ? JSON.parse(searchParams.get('sort')!) : [{ id: "createdAt", desc: true }],
filters: searchParams.get('filters') ? JSON.parse(searchParams.get('filters')!) : [],
joinOperator: (searchParams.get('joinOperator') as "and" | "or") || "and",
- basicFilters: searchParams.get('basicFilters') ?
+ basicFilters: searchParams.get('basicFilters') ?
JSON.parse(searchParams.get('basicFilters')!) : [],
basicJoinOperator: (searchParams.get('basicJoinOperator') as "and" | "or") || "and",
search: searchParams.get('search') || '',
@@ -259,8 +274,8 @@ export function EvaluationTargetsTable({ promises, evaluationYear, className }:
} = useTablePresets<EvaluationTargetWithDepartments>('evaluation-targets-table', initialSettings)
const columns = React.useMemo(
- () => getEvaluationTargetsColumns(),
- []
+ () => getEvaluationTargetsColumns({ setRowAction }),
+ [setRowAction]
)
const filterFields: DataTableFilterField<EvaluationTargetWithDepartments>[] = [
@@ -271,31 +286,41 @@ export function EvaluationTargetsTable({ promises, evaluationYear, className }:
const advancedFilterFields: DataTableAdvancedFilterField<EvaluationTargetWithDepartments>[] = [
{ id: "evaluationYear", label: "평가년도", type: "number" },
- { id: "division", label: "구분", type: "select", options: [
- { label: "해양", value: "OCEAN" },
- { label: "조선", value: "SHIPYARD" },
- ]},
+ {
+ id: "division", label: "구분", type: "select", options: [
+ { label: "해양", value: "OCEAN" },
+ { label: "조선", value: "SHIPYARD" },
+ ]
+ },
{ id: "vendorCode", label: "벤더 코드", type: "text" },
{ id: "vendorName", label: "벤더명", type: "text" },
- { id: "domesticForeign", label: "내외자", type: "select", options: [
- { label: "내자", value: "DOMESTIC" },
- { label: "외자", value: "FOREIGN" },
- ]},
- { id: "materialType", label: "자재구분", type: "select", options: [
- { label: "기자재", value: "EQUIPMENT" },
- { label: "벌크", value: "BULK" },
- { label: "기자재/벌크", value: "EQUIPMENT_BULK" },
- ]},
- { id: "status", label: "상태", type: "select", options: [
- { label: "검토 중", value: "PENDING" },
- { label: "확정", value: "CONFIRMED" },
- { label: "제외", value: "EXCLUDED" },
- ]},
- { id: "consensusStatus", label: "의견 일치", type: "select", options: [
- { label: "의견 일치", value: "true" },
- { label: "의견 불일치", value: "false" },
- { label: "검토 중", value: "null" },
- ]},
+ {
+ id: "domesticForeign", label: "내외자", type: "select", options: [
+ { label: "내자", value: "DOMESTIC" },
+ { label: "외자", value: "FOREIGN" },
+ ]
+ },
+ {
+ id: "materialType", label: "자재구분", type: "select", options: [
+ { label: "기자재", value: "EQUIPMENT" },
+ { label: "벌크", value: "BULK" },
+ { label: "기자재/벌크", value: "EQUIPMENT_BULK" },
+ ]
+ },
+ {
+ id: "status", label: "상태", type: "select", options: [
+ { label: "검토 중", value: "PENDING" },
+ { label: "확정", value: "CONFIRMED" },
+ { label: "제외", value: "EXCLUDED" },
+ ]
+ },
+ {
+ id: "consensusStatus", label: "의견 일치", type: "select", options: [
+ { label: "의견 일치", value: "true" },
+ { label: "의견 불일치", value: "false" },
+ { label: "검토 중", value: "null" },
+ ]
+ },
{ id: "adminComment", label: "관리자 의견", type: "text" },
{ id: "consolidatedComment", label: "종합 의견", type: "text" },
{ id: "confirmedAt", label: "확정일", type: "date" },
@@ -305,17 +330,21 @@ export function EvaluationTargetsTable({ promises, evaluationYear, className }:
const currentSettings = useMemo(() => {
return getCurrentSettings()
}, [getCurrentSettings])
-
+
+ function getColKey<T>(c: ColumnDef<T>): string | undefined {
+ if ("accessorKey" in c && c.accessorKey) return c.accessorKey as string
+ if ("id" in c && c.id) return c.id as string
+ return undefined
+ }
+
const initialState = useMemo(() => {
return {
- sorting: initialSettings.sort.filter(sortItem => {
- const columnExists = columns.some(col => col.accessorKey === sortItem.id || col.id === sortItem.id)
- return columnExists
- }) as any,
+ sorting: initialSettings.sort.filter(s =>
+ columns.some(c => getColKey(c) === s.id)),
columnVisibility: currentSettings.columnVisibility,
columnPinning: currentSettings.pinnedColumns,
}
- }, [currentSettings, initialSettings.sort, columns])
+ }, [columns, currentSettings, initialSettings.sort])
const { table } = useDataTable({
data: tableData.data,
@@ -349,12 +378,12 @@ export function EvaluationTargetsTable({ promises, evaluationYear, className }:
return (
<>
{/* Filter Panel */}
- <div
+ <div
className={cn(
"fixed left-0 bg-background border-r z-50 flex flex-col transition-all duration-300 ease-in-out overflow-hidden",
isFilterPanelOpen ? "border-r shadow-lg" : "border-r-0"
)}
- style={{
+ style={{
width: isFilterPanelOpen ? `${FILTER_PANEL_WIDTH}px` : '0px',
top: `${containerTop}px`,
height: `calc(100vh - ${containerTop}px)`
@@ -362,7 +391,7 @@ export function EvaluationTargetsTable({ promises, evaluationYear, className }:
>
<div className="h-full">
<EvaluationTargetFilterSheet
- isOpen={isFilterPanelOpen}
+ isOpen={isFilterPanelOpen}
onClose={() => setIsFilterPanelOpen(false)}
onSearch={handleSearch}
isLoading={false}
@@ -371,12 +400,12 @@ export function EvaluationTargetsTable({ promises, evaluationYear, className }:
</div>
{/* Main Content Container */}
- <div
+ <div
ref={containerRef}
className={cn("relative w-full overflow-hidden", className)}
>
<div className="flex w-full h-full">
- <div
+ <div
className="flex flex-col min-w-0 overflow-hidden transition-all duration-300 ease-in-out"
style={{
width: isFilterPanelOpen ? `calc(100% - ${FILTER_PANEL_WIDTH}px)` : '100%',
@@ -386,14 +415,14 @@ export function EvaluationTargetsTable({ promises, evaluationYear, className }:
{/* Header Bar */}
<div className="flex items-center justify-between p-4 bg-background shrink-0">
<div className="flex items-center gap-3">
- <Button
- variant="outline"
- size="sm"
+ <Button
+ variant="outline"
+ size="sm"
type='button'
onClick={() => setIsFilterPanelOpen(!isFilterPanelOpen)}
className="flex items-center shadow-sm"
>
- {isFilterPanelOpen ? <PanelLeftClose className="size-4"/> : <PanelLeftOpen className="size-4"/>}
+ {isFilterPanelOpen ? <PanelLeftClose className="size-4" /> : <PanelLeftOpen className="size-4" />}
{getActiveBasicFilterCount() > 0 && (
<span className="ml-2 bg-primary text-primary-foreground rounded-full px-2 py-0.5 text-xs">
{getActiveBasicFilterCount()}
@@ -401,7 +430,7 @@ export function EvaluationTargetsTable({ promises, evaluationYear, className }:
)}
</Button>
</div>
-
+
<div className="text-sm text-muted-foreground">
{tableData && (
<span>총 {tableData.total || tableData.data.length}건</span>
@@ -437,11 +466,18 @@ export function EvaluationTargetsTable({ promises, evaluationYear, className }:
onSetDefaultPreset={setDefaultPreset}
onRenamePreset={renamePreset}
/>
-
+
<EvaluationTargetsTableToolbarActions table={table} />
</div>
</DataTableAdvancedToolbar>
</DataTable>
+
+ <EditEvaluationTargetSheet
+ open={rowAction?.type === "update"}
+ onOpenChange={() => setRowAction(null)}
+ evaluationTarget={rowAction?.row.original ?? null}
+ />
+
</div>
</div>
</div>