diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-04-28 02:13:30 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-04-28 02:13:30 +0000 |
| commit | ef4c533ebacc2cdc97e518f30e9a9350004fcdfb (patch) | |
| tree | 345251a3ed0f4429716fa5edaa31024d8f4cb560 /lib/form-list | |
| parent | 9ceed79cf32c896f8a998399bf1b296506b2cd4a (diff) | |
~20250428 작업사항
Diffstat (limited to 'lib/form-list')
| -rw-r--r-- | lib/form-list/repository.ts | 2 | ||||
| -rw-r--r-- | lib/form-list/service.ts | 2 | ||||
| -rw-r--r-- | lib/form-list/table/formLists-table-toolbar-actions.tsx | 129 | ||||
| -rw-r--r-- | lib/form-list/table/formLists-table.tsx | 7 | ||||
| -rw-r--r-- | lib/form-list/table/meta-sheet.tsx | 85 |
5 files changed, 172 insertions, 53 deletions
diff --git a/lib/form-list/repository.ts b/lib/form-list/repository.ts index d3c555bf..9c7f6891 100644 --- a/lib/form-list/repository.ts +++ b/lib/form-list/repository.ts @@ -36,6 +36,8 @@ export async function selectFormLists( classLabel: tagTypeClassFormMappings.classLabel, formCode: tagTypeClassFormMappings.formCode, formName: tagTypeClassFormMappings.formName, + ep: tagTypeClassFormMappings.ep, + remark: tagTypeClassFormMappings.remark, createdAt: tagTypeClassFormMappings.createdAt, updatedAt: tagTypeClassFormMappings.updatedAt, // 프로젝트 정보 추가 diff --git a/lib/form-list/service.ts b/lib/form-list/service.ts index 310930be..9887609f 100644 --- a/lib/form-list/service.ts +++ b/lib/form-list/service.ts @@ -74,6 +74,8 @@ export async function getFormLists(input: GetFormListsSchema) { offset, limit: input.perPage, }); + + console.log("dbdata") const total = await countFormLists(tx, where); return { data, total }; diff --git a/lib/form-list/table/formLists-table-toolbar-actions.tsx b/lib/form-list/table/formLists-table-toolbar-actions.tsx index 96494607..cf874985 100644 --- a/lib/form-list/table/formLists-table-toolbar-actions.tsx +++ b/lib/form-list/table/formLists-table-toolbar-actions.tsx @@ -2,71 +2,150 @@ import * as React from "react" import { type Table } from "@tanstack/react-table" -import { Download, RefreshCcw, Upload } from "lucide-react" +import { Download, RefreshCcw } from "lucide-react" import { toast } from "sonner" import { exportTableToExcel } from "@/lib/export" import { Button } from "@/components/ui/button" import { ExtendedFormMappings } from "../validation" - - interface ItemsTableToolbarActionsProps { table: Table<ExtendedFormMappings> } export function FormListsTableToolbarActions({ table }: ItemsTableToolbarActionsProps) { const [isLoading, setIsLoading] = React.useState(false) + const [syncId, setSyncId] = React.useState<string | null>(null) + const pollingRef = React.useRef<NodeJS.Timeout | null>(null) + + // Clean up polling on unmount + React.useEffect(() => { + return () => { + if (pollingRef.current) { + clearInterval(pollingRef.current) + } + } + }, []) - const syncForms = async () => { + const startFormSync = async () => { try { setIsLoading(true) - - // API 엔드포인트 호출 - const response = await fetch('/api/cron/forms') - + + // API 엔드포인트 호출 - 작업 시작만 요청 + const response = await fetch('/api/cron/forms/start', { + method: 'POST' + }) + if (!response.ok) { const errorData = await response.json() - throw new Error(errorData.error || 'Failed to sync forms') + throw new Error(errorData.error || 'Failed to start form sync') } - + const data = await response.json() - - // 성공 메시지 표시 - toast.success( - `Forms synced successfully! ${data.result.items} items processed.` - ) - - // 페이지 새로고침으로 테이블 데이터 업데이트 - window.location.reload() + + // 작업 ID 저장 + if (data.syncId) { + setSyncId(data.syncId) + toast.info('Form sync started. This may take a while...') + + // 상태 확인을 위한 폴링 시작 + startPolling(data.syncId) + } else { + throw new Error('No sync ID returned from server') + } } catch (error) { - console.error('Error syncing forms:', error) + console.error('Error starting form sync:', error) toast.error( error instanceof Error ? error.message - : 'An error occurred while syncing forms' + : 'An error occurred while starting form sync' ) - } finally { setIsLoading(false) } } - + + const startPolling = (id: string) => { + // 이전 폴링이 있다면 제거 + if (pollingRef.current) { + clearInterval(pollingRef.current) + } + + // 5초마다 상태 확인 + pollingRef.current = setInterval(async () => { + try { + const response = await fetch(`/api/cron/forms/status?id=${id}`) + + if (!response.ok) { + throw new Error('Failed to get sync status') + } + + const data = await response.json() + + if (data.status === 'completed') { + // 폴링 중지 + if (pollingRef.current) { + clearInterval(pollingRef.current) + pollingRef.current = null + } + + // 상태 초기화 + setIsLoading(false) + setSyncId(null) + + // 성공 메시지 표시 + toast.success( + `Forms synced successfully! ${data.result?.items || 0} items processed.` + ) + + // 테이블 데이터 업데이트 - 전체 페이지 새로고침 대신 데이터만 갱신 + table.resetRowSelection() + // 여기서 테이블 데이터를 다시 불러오는 함수를 호출 + // 예: refetchTableData() + // 또는 SWR/React Query 등을 사용하고 있다면 mutate() 호출 + + // 리로드가 꼭 필요하다면 아래 주석 해제 + // window.location.reload() + } else if (data.status === 'failed') { + // 에러 처리 + if (pollingRef.current) { + clearInterval(pollingRef.current) + pollingRef.current = null + } + + setIsLoading(false) + setSyncId(null) + toast.error(data.error || 'Sync failed') + } else if (data.status === 'processing') { + // 진행 상태 업데이트 (선택적) + if (data.progress) { + toast.info(`Sync in progress: ${data.progress}%`, { + id: `sync-progress-${id}`, + }) + } + } + } catch (error) { + console.error('Error checking sync status:', error) + } + }, 5000) // 5초마다 체크 + } return ( <div className="flex items-center gap-2"> - {/** 4) Export 버튼 */} + {/** Sync Forms 버튼 */} <Button variant="samsung" size="sm" className="gap-2" + onClick={startFormSync} + disabled={isLoading} > <RefreshCcw className={`size-4 ${isLoading ? 'animate-spin' : ''}`} aria-hidden="true" /> <span className="hidden sm:inline"> {isLoading ? 'Syncing...' : 'Get Forms'} </span> </Button> - - {/** 4) Export 버튼 */} + + {/** Export 버튼 */} <Button variant="outline" size="sm" diff --git a/lib/form-list/table/formLists-table.tsx b/lib/form-list/table/formLists-table.tsx index 58ac4671..aa5bfa09 100644 --- a/lib/form-list/table/formLists-table.tsx +++ b/lib/form-list/table/formLists-table.tsx @@ -32,7 +32,6 @@ export function FormListsTable({ promises }: ItemsTableProps) { const [{ data, pageCount }] = React.use(promises) - const [rowAction, setRowAction] = React.useState<DataTableRowAction<ExtendedFormMappings> | null>(null) @@ -100,6 +99,12 @@ export function FormListsTable({ promises }: ItemsTableProps) { type: "text", }, + { + id: "remark", + label: "remark", + type: "text", + + }, { id: "createdAt", diff --git a/lib/form-list/table/meta-sheet.tsx b/lib/form-list/table/meta-sheet.tsx index 694ee845..7c15bdea 100644 --- a/lib/form-list/table/meta-sheet.tsx +++ b/lib/form-list/table/meta-sheet.tsx @@ -1,8 +1,9 @@ "use client" import * as React from "react" -import { useMemo } from "react" +import { useState } from "react" import { Badge } from "@/components/ui/badge" +import { Button } from "@/components/ui/button" import { Sheet, SheetContent, @@ -34,6 +35,56 @@ import { import type { TagTypeClassFormMappings } from "@/db/schema/vendorData" // or your actual type import { fetchFormMetadata, FormColumn } from "@/lib/forms/services" +// 옵션을 표시하기 위한 새로운 컴포넌트 +const CollapsibleOptions = ({ options }: { options?: string[] }) => { + const [isExpanded, setIsExpanded] = useState(false); + + // 옵션이 없거나 5개 이하인 경우 모두 표시 + if (!options || options.length <= 5) { + return ( + <div className="flex flex-wrap gap-1"> + {options?.map((option) => ( + <Badge key={option} variant="outline" className="text-xs"> + {option} + </Badge> + )) || "-"} + </div> + ); + } + + // 더 많은 옵션이 있는 경우 접었다 펼칠 수 있게 함 + return ( + <div> + <div className="flex flex-wrap gap-1 mb-1"> + {isExpanded + ? options.map((option) => ( + <Badge key={option} variant="outline" className="text-xs"> + {option} + </Badge> + )) + : options.slice(0, 3).map((option) => ( + <Badge key={option} variant="outline" className="text-xs"> + {option} + </Badge> + )) + } + {!isExpanded && ( + <Badge variant="outline" className="text-xs bg-muted"> + +{options.length - 3} more + </Badge> + )} + </div> + <Button + variant="ghost" + size="sm" + className="h-6 text-xs px-2 py-0" + onClick={() => setIsExpanded(!isExpanded)} + > + {isExpanded ? "Show less" : "Show all"} + </Button> + </div> + ); +}; interface ViewMetasProps { open: boolean @@ -51,7 +102,7 @@ export function ViewMetas({ open, onOpenChange, form }: ViewMetasProps) { const [loading, setLoading] = React.useState(false) // Group columns by type for better organization - const groupedColumns = useMemo(() => { + const groupedColumns = React.useMemo(() => { if (!metadata?.columns) return {} return metadata.columns.reduce((acc, column) => { @@ -65,7 +116,7 @@ export function ViewMetas({ open, onOpenChange, form }: ViewMetasProps) { }, [metadata]) // Types for the tabs - const columnTypes = useMemo(() => { + const columnTypes = React.useMemo(() => { return Object.keys(groupedColumns) }, [groupedColumns]) @@ -165,17 +216,7 @@ export function ViewMetas({ open, onOpenChange, form }: ViewMetasProps) { <Badge variant="secondary">{column.type}</Badge> </TableCell> <TableCell> - {column.options ? ( - <div className="flex flex-wrap gap-1"> - {column.options.map((option) => ( - <Badge key={option} variant="outline" className="text-xs"> - {option} - </Badge> - ))} - </div> - ) : ( - "-" - )} + <CollapsibleOptions options={column.options} /> </TableCell> </TableRow> ))} @@ -198,7 +239,7 @@ export function ViewMetas({ open, onOpenChange, form }: ViewMetasProps) { <TableRow> <TableHead>Key</TableHead> <TableHead>Label</TableHead> - {type === "select" && <TableHead>Options</TableHead>} + {type === "LIST" && <TableHead>Options</TableHead>} </TableRow> </TableHeader> <TableBody> @@ -206,19 +247,9 @@ export function ViewMetas({ open, onOpenChange, form }: ViewMetasProps) { <TableRow key={column.key}> <TableCell className="font-mono text-sm">{column.key}</TableCell> <TableCell>{column.label}</TableCell> - {type === "select" && ( + {type === "LIST" && ( <TableCell> - {column.options ? ( - <div className="flex flex-wrap gap-1"> - {column.options.map((option) => ( - <Badge key={option} variant="outline" className="text-xs"> - {option} - </Badge> - ))} - </div> - ) : ( - "-" - )} + <CollapsibleOptions options={column.options} /> </TableCell> )} </TableRow> |
