summaryrefslogtreecommitdiff
path: root/components/form-data
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 /components/form-data
parent95bbe9c583ff841220da1267630e7b2025fc36dc (diff)
(대표님) 20250620 작업사항
Diffstat (limited to 'components/form-data')
-rw-r--r--components/form-data/form-data-table.tsx57
-rw-r--r--components/form-data/spreadJS-dialog.tsx375
2 files changed, 359 insertions, 73 deletions
diff --git a/components/form-data/form-data-table.tsx b/components/form-data/form-data-table.tsx
index 61e9897f..b59684e3 100644
--- a/components/form-data/form-data-table.tsx
+++ b/components/form-data/form-data-table.tsx
@@ -57,6 +57,7 @@ import { SEDPConfirmationDialog, SEDPStatusDialog } from "./sedp-components";
import { SEDPCompareDialog } from "./sedp-compare-dialog";
import { getSEDPToken } from "@/lib/sedp/sedp-token";
import { DeleteFormDataDialog } from "./delete-form-data-dialog";
+import { TemplateViewDialog } from "./spreadJS-dialog";
// 기존 fetchTagDataFromSEDP 함수
async function fetchTagDataFromSEDP(projectCode: string, formCode: string): Promise<any> {
@@ -328,12 +329,12 @@ export default function DynamicTable({
try {
setIsLoadingTemplate(true);
-
+
const templateResult = await fetchTemplateFromSEDP(projectCode, formCode);
-
+
setTemplateData(templateResult);
setTemplateDialogOpen(true);
-
+
toast.success("Template data loaded successfully");
} catch (error) {
console.error("Error fetching template:", error);
@@ -720,21 +721,6 @@ export default function DynamicTable({
</Button>
)}
- {/* 새로 추가된 Template 보기 버튼 */}
- <Button
- variant="outline"
- size="sm"
- onClick={handleGetTemplate}
- disabled={isAnyOperationPending || selectedRowCount !== 1}
- >
- {isLoadingTemplate ? (
- <Loader className="mr-2 size-4 animate-spin" />
- ) : (
- <Eye className="mr-2 size-4" />
- )}
- View In Spread
- </Button>
-
{/* 버튼 그룹 */}
<div className="flex items-center gap-2">
{/* 태그 관리 드롭다운 */}
@@ -827,7 +813,25 @@ export default function DynamicTable({
Export
</Button>
-
+ {/* 새로 추가된 Template 보기 버튼 */}
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={handleGetTemplate}
+ disabled={isAnyOperationPending || selectedRowCount !== 1}
+ >
+ {isLoadingTemplate ? (
+ <Loader className="mr-2 size-4 animate-spin" />
+ ) : (
+ <Eye className="mr-2 size-4" />
+ )}
+ View Template
+ {selectedRowCount === 1 && (
+ <span className="ml-2 text-xs bg-green-100 text-green-700 px-2 py-1 rounded">
+ 1
+ </span>
+ )}
+ </Button>
{/* COMPARE WITH SEDP 버튼 */}
<Button
@@ -918,6 +922,18 @@ export default function DynamicTable({
templateData={templateData}
selectedRow={selectedRowsData[0]}
formCode={formCode}
+ contractItemId={contractItemId}
+ onUpdateSuccess={(updatedValues) => {
+ // SpreadSheets에서 업데이트된 값을 테이블에 반영
+ const tagNo = updatedValues.TAG_NO;
+ if (tagNo) {
+ setTableData(prev =>
+ prev.map(item =>
+ item.TAG_NO === tagNo ? updatedValues : item
+ )
+ );
+ }
+ }}
/>
{/* SEDP Confirmation Dialog */}
@@ -989,5 +1005,4 @@ export default function DynamicTable({
)}
</>
);
-}
-
+} \ No newline at end of file
diff --git a/components/form-data/spreadJS-dialog.tsx b/components/form-data/spreadJS-dialog.tsx
index 69232508..4a8550cb 100644
--- a/components/form-data/spreadJS-dialog.tsx
+++ b/components/form-data/spreadJS-dialog.tsx
@@ -1,69 +1,340 @@
+"use client";
+
import * as React from "react";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { GenericData } from "./export-excel-form";
import { SpreadSheets, Worksheet, Column } from "@mescius/spread-sheets-react";
import * as GC from "@mescius/spread-sheets";
+import { toast } from "sonner";
+import { updateFormDataInDB } from "@/lib/forms/services";
+import { Loader, Save } from "lucide-react";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+interface TemplateItem {
+ TMPL_ID: string;
+ NAME: string;
+ TMPL_TYPE: string;
+ SPR_LST_SETUP: {
+ ACT_SHEET: string;
+ HIDN_SHEETS: Array<string>;
+ CONTENT?: string; // SpreadSheets JSON
+ DATA_SHEETS: Array<{
+ SHEET_NAME: string;
+ REG_TYPE_ID: string;
+ MAP_CELL_ATT: Array<{
+ ATT_ID: string;
+ IN: string;
+ }>;
+ }>;
+ };
+ GRD_LST_SETUP: {
+ REG_TYPE_ID: string;
+ SPR_ITM_IDS: Array<string>;
+ ATTS: Array<{}>;
+ };
+ SPR_ITM_LST_SETUP: {
+ ACT_SHEET: string;
+ HIDN_SHEETS: Array<string>;
+ CONTENT?: string; // SpreadSheets JSON
+ DATA_SHEETS: Array<{
+ SHEET_NAME: string;
+ REG_TYPE_ID: string;
+ MAP_CELL_ATT: Array<{
+ ATT_ID: string;
+ IN: string;
+ }>;
+ }>;
+ };
+}
interface TemplateViewDialogProps {
- isOpen: boolean;
- onClose: () => void;
- templateData: any;
- selectedRow: GenericData;
- formCode: string;
- }
+ isOpen: boolean;
+ onClose: () => void;
+ templateData: TemplateItem[] | any; // 배열 또는 기존 형태
+ selectedRow: GenericData;
+ formCode: string;
+ contractItemId: number;
+ /** 업데이트 성공 시 호출될 콜백 */
+ onUpdateSuccess?: (updatedValues: Record<string, any>) => void;
+}
export function TemplateViewDialog({
- isOpen,
- onClose,
- templateData,
- selectedRow,
- formCode
- }: TemplateViewDialogProps) {
- return (
- <Dialog open={isOpen} onOpenChange={onClose}>
- <DialogContent className="w-[80%] max-w-none h-[80vh] flex flex-col" style={{maxWidth:"80vw"}}>
- <DialogHeader className="flex-shrink-0">
- <DialogTitle>SEDP Template - {formCode}</DialogTitle>
- <DialogDescription>
- {selectedRow && `Selected TAG_NO: ${selectedRow.TAG_NO || 'N/A'}`}
- </DialogDescription>
- </DialogHeader>
-
- {/* 스크롤 가능한 콘텐츠 영역 */}
- <div className="flex-1 overflow-y-auto space-y-4 py-4">
- {/* 여기에 템플릿 데이터나 다른 콘텐츠가 들어갈 예정 */}
- <div className="space-y-4">
- <p className="text-sm text-muted-foreground">
- Template content will be displayed here...
- </p>
-
- {/* 임시로 templateData 표시 */}
- {templateData && (
- <pre className="bg-muted p-4 rounded text-sm overflow-auto">
- {JSON.stringify(templateData, null, 2)}
- </pre>
- )}
+ isOpen,
+ onClose,
+ templateData,
+ selectedRow,
+ formCode,
+ contractItemId,
+ onUpdateSuccess
+}: TemplateViewDialogProps) {
+ const [hostStyle, setHostStyle] = React.useState({
+ width: '100%',
+ height: '100%'
+ });
+
+ const [isPending, setIsPending] = React.useState(false);
+ const [hasChanges, setHasChanges] = React.useState(false);
+ const [currentSpread, setCurrentSpread] = React.useState<any>(null);
+ const [selectedTemplateId, setSelectedTemplateId] = React.useState<string>("");
+
+ // 템플릿 데이터를 배열로 정규화하고 CONTENT가 있는 것만 필터링
+ const normalizedTemplates = React.useMemo((): TemplateItem[] => {
+ if (!templateData) return [];
+
+ let templates: TemplateItem[];
+ // 이미 배열인 경우
+ if (Array.isArray(templateData)) {
+ templates = templateData as TemplateItem[];
+ } else {
+ // 기존 형태인 경우 (하위 호환성)
+ templates = [templateData as TemplateItem];
+ }
+
+ // CONTENT가 있는 템플릿만 필터링
+ return templates.filter(template => {
+ const sprContent = template.SPR_LST_SETUP?.CONTENT;
+ const sprItmContent = template.SPR_ITM_LST_SETUP?.CONTENT;
+ return sprContent || sprItmContent;
+ });
+ }, [templateData]);
+
+ // 선택된 템플릿 가져오기
+ const selectedTemplate = React.useMemo(() => {
+ if (!selectedTemplateId) return normalizedTemplates[0]; // 기본값: 첫 번째 템플릿
+ return normalizedTemplates.find(t => t.TMPL_ID === selectedTemplateId) || normalizedTemplates[0];
+ }, [normalizedTemplates, selectedTemplateId]);
+
+ // 템플릿 변경 시 기본 선택
+ React.useEffect(() => {
+ if (normalizedTemplates.length > 0 && !selectedTemplateId) {
+ setSelectedTemplateId(normalizedTemplates[0].TMPL_ID);
+ }
+ }, [normalizedTemplates, selectedTemplateId]);
+
+ const initSpread = React.useCallback((spread: any) => {
+ if (!spread || !selectedTemplate) return;
+
+ try {
+ setCurrentSpread(spread);
+ setHasChanges(false); // 템플릿 로드 시 변경사항 초기화
+
+ // CONTENT 찾기 (SPR_LST_SETUP 또는 SPR_ITM_LST_SETUP 중 하나)
+ let contentJson = null;
+ if (selectedTemplate.SPR_LST_SETUP?.CONTENT) {
+ contentJson = selectedTemplate.SPR_LST_SETUP.CONTENT;
+ console.log('Using SPR_LST_SETUP.CONTENT for template:', selectedTemplate.NAME);
+ } else if (selectedTemplate.SPR_ITM_LST_SETUP?.CONTENT) {
+ contentJson = selectedTemplate.SPR_ITM_LST_SETUP.CONTENT;
+ console.log('Using SPR_ITM_LST_SETUP.CONTENT for template:', selectedTemplate.NAME);
+ }
+
+ if (contentJson) {
+ console.log('Loading template content for:', selectedTemplate.NAME);
+
+ const jsonData = typeof contentJson === 'string'
+ ? JSON.parse(contentJson)
+ : contentJson;
+
+ // fromJSON으로 템플릿 구조 로드
+ spread.fromJSON(jsonData);
+ } else {
+ console.warn('No CONTENT found in template:', selectedTemplate.NAME);
+ return;
+ }
+
+ // 값 변경 이벤트 리스너 추가 (간단한 변경사항 감지만)
+ const activeSheet = spread.getActiveSheet();
+
+ activeSheet.bind(GC.Spread.Sheets.Events.CellChanged, (event: any, info: any) => {
+ console.log('Cell changed:', info);
+ setHasChanges(true);
+ });
+
+ activeSheet.bind(GC.Spread.Sheets.Events.ValueChanged, (event: any, info: any) => {
+ console.log('Value changed:', info);
+ setHasChanges(true);
+ });
+
+ } catch (error) {
+ console.error('Error initializing spread:', error);
+ toast.error('Failed to load template');
+ }
+ }, [selectedTemplate]);
+
+ // 템플릿 변경 핸들러
+ const handleTemplateChange = (templateId: string) => {
+ setSelectedTemplateId(templateId);
+ setHasChanges(false); // 템플릿 변경 시 변경사항 초기화
+
+ // SpreadSheets 재초기화는 useCallback 의존성에 의해 자동으로 처리됨
+ if (currentSpread) {
+ // 강제로 재초기화
+ setTimeout(() => {
+ initSpread(currentSpread);
+ }, 100);
+ }
+ };
+
+ // 변경사항 저장 함수
+ const handleSaveChanges = React.useCallback(async () => {
+ if (!currentSpread || !hasChanges) {
+ toast.info("No changes to save");
+ return;
+ }
+
+ try {
+ setIsPending(true);
+
+ // SpreadSheets에서 현재 데이터를 JSON으로 추출
+ const spreadJson = currentSpread.toJSON();
+ console.log('Current spread data:', spreadJson);
+
+ // 실제 데이터 추출 방법은 SpreadSheets 구조에 따라 달라질 수 있음
+ // 여기서는 기본적인 예시만 제공
+ const activeSheet = currentSpread.getActiveSheet();
+
+ // 간단한 예시: 특정 범위의 데이터를 추출하여 selectedRow 형태로 변환
+ // 실제 구현에서는 템플릿의 구조에 맞춰 데이터를 추출해야 함
+ const dataToSave = {
+ ...selectedRow, // 기본값으로 원본 데이터 사용
+ // 여기에 SpreadSheets에서 변경된 값들을 추가
+ // 예: TAG_DESC: activeSheet.getValue(특정행, 특정열)
+ };
+
+ // TAG_NO는 절대 변경되지 않도록 원본 값으로 강제 설정
+ dataToSave.TAG_NO = selectedRow?.TAG_NO;
+
+ console.log('Data to save (TAG_NO preserved):', dataToSave);
+
+ const { success, message } = await updateFormDataInDB(
+ formCode,
+ contractItemId,
+ dataToSave
+ );
+
+ if (!success) {
+ toast.error(message);
+ return;
+ }
+
+ toast.success("Changes saved successfully!");
+
+ const updatedData = {
+ ...selectedRow,
+ ...dataToSave,
+ };
+
+ onUpdateSuccess?.(updatedData);
+ setHasChanges(false);
+
+ } catch (error) {
+ console.error("Error saving changes:", error);
+ toast.error("An unexpected error occurred while saving");
+ } finally {
+ setIsPending(false);
+ }
+ }, [currentSpread, hasChanges, formCode, contractItemId, selectedRow, onUpdateSuccess]);
+
+ if (!isOpen) return null;
+
+ return (
+ <Dialog open={isOpen} onOpenChange={onClose}>
+ <DialogContent
+ className="w-[80%] max-w-none h-[80vh] flex flex-col"
+ style={{maxWidth:"80vw"}}
+ >
+ <DialogHeader className="flex-shrink-0">
+ <DialogTitle>SEDP Template - {formCode}</DialogTitle>
+ <DialogDescription>
+ {selectedRow && `Selected TAG_NO: ${selectedRow.TAG_NO || 'N/A'}`}
+ {hasChanges && (
+ <span className="ml-2 text-orange-600 font-medium">
+ • Unsaved changes
+ </span>
+ )}
+ <br />
+ <span className="text-xs text-muted-foreground">
+ Template content will be loaded directly. Manual data entry may be required.
+ </span>
+ </DialogDescription>
+ </DialogHeader>
+
+ {/* 템플릿 선택 UI */}
+ {normalizedTemplates.length > 1 && (
+ <div className="flex-shrink-0 px-4 py-2 border-b">
+ <div className="flex items-center gap-2">
+ <label className="text-sm font-medium">Template:</label>
+ <Select value={selectedTemplateId} onValueChange={handleTemplateChange}>
+ <SelectTrigger className="w-64">
+ <SelectValue placeholder="Select a template" />
+ </SelectTrigger>
+ <SelectContent>
+ {normalizedTemplates.map((template) => (
+ <SelectItem key={template.TMPL_ID} value={template.TMPL_ID}>
+ <div className="flex flex-col">
+ <span>{template.NAME || `Template ${template.TMPL_ID.slice(0, 8)}`}</span>
+ <span className="text-xs text-muted-foreground">{template.TMPL_TYPE}</span>
+ </div>
+ </SelectItem>
+ ))}
+ </SelectContent>
+ </Select>
+ <span className="text-xs text-muted-foreground">
+ ({normalizedTemplates.length} templates available)
+ </span>
</div>
</div>
-
- <DialogFooter className="flex-shrink-0">
- <Button variant="outline" onClick={onClose}>
- Close
- </Button>
+ )}
+
+ {/* SpreadSheets 컴포넌트 영역 */}
+ <div className="flex-1 overflow-hidden">
+ {selectedTemplate ? (
+ <SpreadSheets
+ key={selectedTemplateId} // 템플릿 변경 시 컴포넌트 재생성
+ workbookInitialized={initSpread}
+ hostStyle={hostStyle}
+ />
+ ) : (
+ <div className="flex items-center justify-center h-full text-muted-foreground">
+ No template available
+ </div>
+ )}
+ </div>
+
+ <DialogFooter className="flex-shrink-0">
+ <Button variant="outline" onClick={onClose}>
+ Close
+ </Button>
+
+ {hasChanges && (
<Button
variant="default"
- onClick={() => {
- // 여기에 Template 적용 로직 추가 가능
- console.log('Apply template logic here');
- onClose();
- }}
+ onClick={handleSaveChanges}
+ disabled={isPending}
>
- Apply Template
+ {isPending ? (
+ <>
+ <Loader className="mr-2 h-4 w-4 animate-spin" />
+ Saving...
+ </>
+ ) : (
+ <>
+ <Save className="mr-2 h-4 w-4" />
+ Save Changes
+ </>
+ )}
</Button>
- </DialogFooter>
- </DialogContent>
- </Dialog>
- );
- } \ No newline at end of file
+ )}
+
+ </DialogFooter>
+ </DialogContent>
+ </Dialog>
+ );
+} \ No newline at end of file