diff options
Diffstat (limited to 'components/form-data')
| -rw-r--r-- | components/form-data/form-data-table.tsx | 190 |
1 files changed, 148 insertions, 42 deletions
diff --git a/components/form-data/form-data-table.tsx b/components/form-data/form-data-table.tsx index d5d79735..9dbcb627 100644 --- a/components/form-data/form-data-table.tsx +++ b/components/form-data/form-data-table.tsx @@ -19,7 +19,7 @@ import { Upload, Plus, Tag, - TagsIcon, + TagsIcon, FileOutput, Clipboard, Send, @@ -115,7 +115,97 @@ export default function DynamicTable({ const [formStats, setFormStats] = React.useState<FormStatusByVendor | null>(null); const [isLoadingStats, setIsLoadingStats] = React.useState(true); + const [activeFilter, setActiveFilter] = React.useState<string | null>(null); + const [filteredTableData, setFilteredTableData] = React.useState<GenericData[]>(tableData); + // 필터링 로직 + React.useEffect(() => { + if (!activeFilter) { + setFilteredTableData(tableData); + return; + } + + const today = new Date(); + today.setHours(0, 0, 0, 0); + const sevenDaysLater = new Date(today); + sevenDaysLater.setDate(sevenDaysLater.getDate() + 7); + + let filtered = [...tableData]; + + switch (activeFilter) { + case 'completed': + // 모든 필수 필드가 완료된 태그만 표시 + filtered = tableData.filter(item => { + const tagEditableFields = editableFieldsMap.get(item.TAG_NO) || []; + return columnsJSON + .filter(col => (col.shi === 'IN' || col.shi === 'BOTH') && tagEditableFields.includes(col.key)) + .every(col => { + const value = item[col.key]; + return value !== undefined && value !== null && value !== ''; + }); + }); + break; + + case 'remaining': + // 미완료 필드가 있는 태그만 표시 + filtered = tableData.filter(item => { + const tagEditableFields = editableFieldsMap.get(item.TAG_NO) || []; + return columnsJSON + .filter(col => (col.shi === 'IN' || col.shi === 'BOTH') && tagEditableFields.includes(col.key)) + .some(col => { + const value = item[col.key]; + return value === undefined || value === null || value === ''; + }); + }); + break; + + case 'upcoming': + // 7일 이내 임박한 태그만 표시 + filtered = tableData.filter(item => { + const dueDate = item.DUE_DATE; + if (!dueDate) return false; + + const target = new Date(dueDate); + target.setHours(0, 0, 0, 0); + + // 미완료이면서 7일 이내인 경우 + const hasIncompleteFields = columnsJSON + .filter(col => col.shi === 'IN' || col.shi === 'BOTH') + .some(col => !item[col.key]); + + return hasIncompleteFields && target >= today && target <= sevenDaysLater; + }); + break; + + case 'overdue': + // 지연된 태그만 표시 + filtered = tableData.filter(item => { + const dueDate = item.DUE_DATE; + if (!dueDate) return false; + + const target = new Date(dueDate); + target.setHours(0, 0, 0, 0); + + // 미완료이면서 지연된 경우 + const hasIncompleteFields = columnsJSON + .filter(col => col.shi === 'IN' || col.shi === 'BOTH') + .some(col => !item[col.key]); + + return hasIncompleteFields && target < today; + }); + break; + + default: + filtered = tableData; + } + + setFilteredTableData(filtered); + }, [activeFilter, tableData, columnsJSON, editableFieldsMap]); + + // 카드 클릭 핸들러 + const handleCardClick = (filterType: string | null) => { + setActiveFilter(prev => prev === filterType ? null : filterType); + }; React.useEffect(() => { const fetchFormStats = async () => { @@ -310,7 +400,7 @@ export default function DynamicTable({ isArray: Array.isArray(templateResult), data: templateResult }); - + if (Array.isArray(templateResult)) { templateResult.forEach((tmpl, idx) => { console.log(` [${idx}] TMPL_ID: ${tmpl?.TMPL_ID || 'MISSING'}, NAME: ${tmpl?.NAME || 'N/A'}, TYPE: ${tmpl?.TMPL_TYPE || 'N/A'}`); @@ -687,11 +777,15 @@ export default function DynamicTable({ return ( <> - + <div className="mb-6"> <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-6"> - {/* Tag Count */} - <Card> + {/* Total Tags Card - 클릭 시 전체 보기 */} + <Card + className={`cursor-pointer transition-all ${activeFilter === null ? 'ring-2 ring-primary' : 'hover:shadow-lg' + }`} + onClick={() => handleCardClick(null)} + > <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> <CardTitle className="text-sm font-medium"> Total Tags @@ -707,35 +801,17 @@ export default function DynamicTable({ )} </div> <p className="text-xs text-muted-foreground"> - Total Tag Count + {activeFilter === null ? 'Showing all' : 'Click to show all'} </p> </CardContent> </Card> - {/* Completion Rate */} - <Card> - <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> - <CardTitle className="text-sm font-medium"> - Completion - </CardTitle> - <Target className="h-4 w-4 text-muted-foreground" /> - </CardHeader> - <CardContent> - <div className="text-2xl font-bold"> - {isLoadingStats ? ( - <span className="animate-pulse">-</span> - ) : ( - `${formStats?.completionRate || 0}%` - )} - </div> - <p className="text-xs text-muted-foreground"> - {formStats ? `${formStats.completedFields} / ${formStats.totalFields}` : '-'} - </p> - </CardContent> - </Card> - - {/* Completed Fields */} - <Card> + {/* Completed Fields Card */} + <Card + className={`cursor-pointer transition-all ${activeFilter === 'completed' ? 'ring-2 ring-green-600' : 'hover:shadow-lg' + }`} + onClick={() => handleCardClick('completed')} + > <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> <CardTitle className="text-sm font-medium"> Completed @@ -751,13 +827,17 @@ export default function DynamicTable({ )} </div> <p className="text-xs text-muted-foreground"> - Completed Fields + {activeFilter === 'completed' ? 'Filtering active' : 'Click to filter'} </p> </CardContent> </Card> - {/* Remaining Fields */} - <Card> + {/* Remaining Fields Card */} + <Card + className={`cursor-pointer transition-all ${activeFilter === 'remaining' ? 'ring-2 ring-blue-600' : 'hover:shadow-lg' + }`} + onClick={() => handleCardClick('remaining')} + > <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> <CardTitle className="text-sm font-medium"> Remaining @@ -773,13 +853,17 @@ export default function DynamicTable({ )} </div> <p className="text-xs text-muted-foreground"> - Remaining Fields + {activeFilter === 'remaining' ? 'Filtering active' : 'Click to filter'} </p> </CardContent> </Card> - {/* Upcoming (7 days) */} - <Card> + {/* Upcoming Card */} + <Card + className={`cursor-pointer transition-all ${activeFilter === 'upcoming' ? 'ring-2 ring-yellow-600' : 'hover:shadow-lg' + }`} + onClick={() => handleCardClick('upcoming')} + > <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> <CardTitle className="text-sm font-medium"> Upcoming @@ -795,13 +879,17 @@ export default function DynamicTable({ )} </div> <p className="text-xs text-muted-foreground"> - Due in 7 Days + {activeFilter === 'upcoming' ? 'Filtering active' : 'Click to filter'} </p> </CardContent> </Card> - {/* Overdue */} - <Card> + {/* Overdue Card */} + <Card + className={`cursor-pointer transition-all ${activeFilter === 'overdue' ? 'ring-2 ring-red-600' : 'hover:shadow-lg' + }`} + onClick={() => handleCardClick('overdue')} + > <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> <CardTitle className="text-sm font-medium"> Overdue @@ -817,22 +905,40 @@ export default function DynamicTable({ )} </div> <p className="text-xs text-muted-foreground"> - Overdue + {activeFilter === 'overdue' ? 'Filtering active' : 'Click to filter'} </p> </CardContent> </Card> </div> </div> - + <ClientDataTable - data={tableData} + data={filteredTableData} // tableData 대신 filteredTableData 사용 columns={columns} advancedFilterFields={advancedFilterFields} autoSizeColumns onSelectedRowsChange={setSelectedRowsData} clearSelection={clearSelection} > + {/* 필터 상태 표시 */} + {activeFilter && ( + <div className="flex items-center gap-2 mr-auto"> + <span className="text-sm text-muted-foreground"> + Filter: {activeFilter === 'completed' ? 'Completed' : + activeFilter === 'remaining' ? 'Remaining' : + activeFilter === 'upcoming' ? 'Upcoming (7 days)' : + activeFilter === 'overdue' ? 'Overdue' : 'All'} + </span> + <Button + variant="ghost" + size="sm" + onClick={() => setActiveFilter(null)} + > + Clear filter + </Button> + </div> + )} {/* 선택된 항목 수 표시 (선택된 항목이 있을 때만) */} {selectedRowCount > 0 && ( <Button |
