summaryrefslogtreecommitdiff
path: root/components/form-data/form-data-table.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'components/form-data/form-data-table.tsx')
-rw-r--r--components/form-data/form-data-table.tsx190
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