모달 크기 고정
This commit is contained in:
parent
bc66f3bba1
commit
9e956999c5
|
|
@ -7,13 +7,13 @@
|
|||
|
||||
import { useState, useEffect } from "react";
|
||||
import {
|
||||
ResizableDialog,
|
||||
ResizableDialogContent,
|
||||
ResizableDialogHeader,
|
||||
ResizableDialogTitle,
|
||||
ResizableDialogDescription,
|
||||
ResizableDialogFooter,
|
||||
} from "@/components/ui/resizable-dialog";
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
|
@ -197,14 +197,14 @@ export function AddColumnModal({ isOpen, onClose, tableName, onSuccess }: AddCol
|
|||
const inputTypeOption = INPUT_TYPE_OPTIONS.find((opt) => opt.value === column.inputType);
|
||||
|
||||
return (
|
||||
<ResizableDialog open={isOpen} onOpenChange={onClose}>
|
||||
<ResizableDialogContent className="max-w-2xl">
|
||||
<ResizableDialogHeader>
|
||||
<ResizableDialogTitle className="flex items-center gap-2">
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Plus className="h-5 w-5" />
|
||||
컬럼 추가 - {tableName}
|
||||
</ResizableDialogTitle>
|
||||
</ResizableDialogHeader>
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* 검증 오류 표시 */}
|
||||
|
|
@ -346,7 +346,7 @@ export function AddColumnModal({ isOpen, onClose, tableName, onSuccess }: AddCol
|
|||
</Alert>
|
||||
</div>
|
||||
|
||||
<ResizableDialogFooter>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={onClose} disabled={loading}>
|
||||
취소
|
||||
</Button>
|
||||
|
|
@ -365,8 +365,8 @@ export function AddColumnModal({ isOpen, onClose, tableName, onSuccess }: AddCol
|
|||
"컬럼 추가"
|
||||
)}
|
||||
</Button>
|
||||
</ResizableDialogFooter>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -198,7 +198,7 @@ export default function AdvancedBatchModal({
|
|||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="max-w-[95vw] sm:max-w-[800px] max-h-[90vh] overflow-y-auto">
|
||||
<DialogContent className="max-w-[95vw] sm:max-w-[800px] max-h-[90vh] overflow-hidden">
|
||||
<DialogHeader>
|
||||
<DialogTitle>고급 배치 생성</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
DialogHeader,
|
||||
|
||||
|
||||
} from "@/components/ui/resizable-dialog";
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
|
@ -169,13 +169,13 @@ export default function BatchJobModal({
|
|||
// 상태 제거 - 필요없음
|
||||
|
||||
return (
|
||||
<ResizableDialog open={isOpen} onOpenChange={onClose}>
|
||||
<ResizableDialogContent className="max-w-[95vw] sm:max-w-[600px]">
|
||||
<ResizableDialogHeader>
|
||||
<ResizableDialogTitle className="text-base sm:text-lg">
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="max-w-[95vw] sm:max-w-[600px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-base sm:text-lg">
|
||||
{job ? "배치 작업 수정" : "새 배치 작업"}
|
||||
</ResizableDialogTitle>
|
||||
</ResizableDialogHeader>
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-3 sm:space-y-4">
|
||||
{/* 기본 정보 */}
|
||||
|
|
@ -344,7 +344,7 @@ export default function BatchJobModal({
|
|||
</Badge>
|
||||
</div>
|
||||
|
||||
<ResizableDialogFooter className="gap-2 sm:gap-0">
|
||||
<DialogFooter className="gap-2 sm:gap-0">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
|
|
@ -360,9 +360,9 @@ export default function BatchJobModal({
|
|||
>
|
||||
{isLoading ? "저장 중..." : "저장"}
|
||||
</Button>
|
||||
</ResizableDialogFooter>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { ResizableDialog, ResizableDialogContent, ResizableDialogHeader, DialogTitle } from "@/components/ui/resizable-dialog";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
|
|
@ -164,11 +164,11 @@ export function CodeCategoryFormModal({
|
|||
const isLoading = createCategoryMutation.isPending || updateCategoryMutation.isPending;
|
||||
|
||||
return (
|
||||
<ResizableDialog open={isOpen} onOpenChange={onClose}>
|
||||
<ResizableDialogContent className="max-w-[95vw] sm:max-w-[500px]">
|
||||
<ResizableDialogHeader>
|
||||
<ResizableDialogTitle className="text-base sm:text-lg">{isEditing ? "카테고리 수정" : "새 카테고리"}</ResizableDialogTitle>
|
||||
</ResizableDialogHeader>
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="max-w-[95vw] sm:max-w-[500px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-base sm:text-lg">{isEditing ? "카테고리 수정" : "새 카테고리"}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-3 sm:space-y-4">
|
||||
{/* 카테고리 코드 */}
|
||||
|
|
@ -383,7 +383,7 @@ export function CodeCategoryFormModal({
|
|||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { ResizableDialog, ResizableDialogContent, ResizableDialogHeader, DialogTitle } from "@/components/ui/resizable-dialog";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
|
|
@ -153,11 +153,11 @@ export function CodeFormModal({ isOpen, onClose, categoryCode, editingCode, code
|
|||
const isLoading = createCodeMutation.isPending || updateCodeMutation.isPending;
|
||||
|
||||
return (
|
||||
<ResizableDialog open={isOpen} onOpenChange={onClose}>
|
||||
<ResizableDialogContent className="max-w-[95vw] sm:max-w-[500px]">
|
||||
<ResizableDialogHeader>
|
||||
<ResizableDialogTitle className="text-base sm:text-lg">{isEditing ? "코드 수정" : "새 코드"}</ResizableDialogTitle>
|
||||
</ResizableDialogHeader>
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="max-w-[95vw] sm:max-w-[500px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-base sm:text-lg">{isEditing ? "코드 수정" : "새 코드"}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-3 sm:space-y-4">
|
||||
{/* 코드값 */}
|
||||
|
|
@ -328,7 +328,7 @@ export function CodeFormModal({ isOpen, onClose, categoryCode, editingCode, code
|
|||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
DialogHeader,
|
||||
|
||||
|
||||
} from "@/components/ui/resizable-dialog";
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
|
@ -164,13 +164,13 @@ export default function CollectionConfigModal({
|
|||
];
|
||||
|
||||
return (
|
||||
<ResizableDialog open={isOpen} onOpenChange={onClose}>
|
||||
<ResizableDialogContent className="max-w-2xl">
|
||||
<ResizableDialogHeader>
|
||||
<ResizableDialogTitle>
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{config ? "수집 설정 수정" : "새 수집 설정"}
|
||||
</ResizableDialogTitle>
|
||||
</ResizableDialogHeader>
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
{/* 기본 정보 */}
|
||||
|
|
@ -331,16 +331,16 @@ export default function CollectionConfigModal({
|
|||
<Label htmlFor="is_active">활성화</Label>
|
||||
</div>
|
||||
|
||||
<ResizableDialogFooter>
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="outline" onClick={onClose}>
|
||||
취소
|
||||
</Button>
|
||||
<Button type="submit" disabled={isLoading}>
|
||||
{isLoading ? "저장 중..." : "저장"}
|
||||
</Button>
|
||||
</ResizableDialogFooter>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@ import { Button } from "@/components/ui/button";
|
|||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
ResizableDialog,
|
||||
ResizableDialogContent,
|
||||
ResizableDialogHeader,
|
||||
ResizableDialogTitle,
|
||||
ResizableDialogDescription,
|
||||
ResizableDialogFooter,
|
||||
} from "@/components/ui/resizable-dialog";
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import { LoadingSpinner } from "@/components/common/LoadingSpinner";
|
||||
import { validateBusinessNumber, formatBusinessNumber } from "@/lib/validation/businessNumber";
|
||||
|
||||
|
|
@ -111,8 +111,8 @@ export function CompanyFormModal({
|
|||
};
|
||||
|
||||
return (
|
||||
<ResizableDialog open={modalState.isOpen} onOpenChange={handleCancel}>
|
||||
<ResizableDialogContent
|
||||
<Dialog open={modalState.isOpen} onOpenChange={handleCancel}>
|
||||
<DialogContent
|
||||
className="sm:max-w-[425px]"
|
||||
onKeyDown={handleKeyDown}
|
||||
defaultWidth={500}
|
||||
|
|
@ -124,9 +124,9 @@ export function CompanyFormModal({
|
|||
modalId="company-form"
|
||||
userId={modalState.companyCode}
|
||||
>
|
||||
<ResizableDialogHeader>
|
||||
<ResizableDialogTitle>{isEditMode ? "회사 정보 수정" : "새 회사 등록"}</ResizableDialogTitle>
|
||||
</ResizableDialogHeader>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{isEditMode ? "회사 정보 수정" : "새 회사 등록"}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4 py-4">
|
||||
{/* 회사명 입력 (필수) */}
|
||||
|
|
@ -255,7 +255,7 @@ export function CompanyFormModal({
|
|||
)}
|
||||
</div>
|
||||
|
||||
<ResizableDialogFooter>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={handleCancel} disabled={isLoading || isSaving}>
|
||||
취소
|
||||
</Button>
|
||||
|
|
@ -273,8 +273,8 @@ export function CompanyFormModal({
|
|||
{(isLoading || isSaving) && <LoadingSpinner className="mr-2 h-4 w-4" />}
|
||||
{isEditMode ? "수정" : "등록"}
|
||||
</Button>
|
||||
</ResizableDialogFooter>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,13 +7,13 @@
|
|||
|
||||
import { useState, useEffect } from "react";
|
||||
import {
|
||||
ResizableDialog,
|
||||
ResizableDialogContent,
|
||||
ResizableDialogHeader,
|
||||
ResizableDialogTitle,
|
||||
ResizableDialogDescription,
|
||||
ResizableDialogFooter,
|
||||
} from "@/components/ui/resizable-dialog";
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
|
@ -321,20 +321,20 @@ export function CreateTableModal({
|
|||
const isFormValid = !tableNameError && tableName && columns.some((col) => col.name && col.inputType);
|
||||
|
||||
return (
|
||||
<ResizableDialog open={isOpen} onOpenChange={onClose}>
|
||||
<ResizableDialogContent className="max-h-[90vh] max-w-6xl overflow-y-auto">
|
||||
<ResizableDialogHeader>
|
||||
<ResizableDialogTitle className="flex items-center gap-2">
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="max-h-[90vh] max-w-6xl overflow-hidden">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Plus className="h-5 w-5" />
|
||||
{isDuplicateMode ? "테이블 복제" : "새 테이블 생성"}
|
||||
</ResizableDialogTitle>
|
||||
<ResizableDialogDescription>
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
{isDuplicateMode
|
||||
? `${sourceTableName} 테이블을 복제하여 새 테이블을 생성합니다. 테이블명을 입력하고 필요시 컬럼을 수정하세요.`
|
||||
: "최고 관리자만 새로운 테이블을 생성할 수 있습니다. 테이블명과 컬럼 정의를 입력하고 검증 후 생성하세요."
|
||||
}
|
||||
</ResizableDialogDescription>
|
||||
</ResizableDialogHeader>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* 테이블 기본 정보 */}
|
||||
|
|
@ -452,7 +452,7 @@ export function CreateTableModal({
|
|||
)}
|
||||
</div>
|
||||
|
||||
<ResizableDialogFooter className="gap-2">
|
||||
<DialogFooter className="gap-2">
|
||||
<Button variant="outline" onClick={onClose} disabled={loading}>
|
||||
취소
|
||||
</Button>
|
||||
|
|
@ -482,8 +482,8 @@ export function CreateTableModal({
|
|||
isDuplicateMode ? "복제 생성" : "테이블 생성"
|
||||
)}
|
||||
</Button>
|
||||
</ResizableDialogFooter>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@
|
|||
|
||||
import { useState, useEffect } from "react";
|
||||
import {
|
||||
ResizableDialog,
|
||||
ResizableDialogContent,
|
||||
ResizableDialogHeader,
|
||||
ResizableDialogTitle,
|
||||
ResizableDialogFooter
|
||||
} from "@/components/ui/resizable-dialog";
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogFooter
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
|
@ -148,14 +148,14 @@ export function DDLLogViewer({ isOpen, onClose }: DDLLogViewerProps) {
|
|||
};
|
||||
|
||||
return (
|
||||
<ResizableDialog open={isOpen} onOpenChange={onClose}>
|
||||
<ResizableDialogContent className="max-h-[90vh] max-w-7xl overflow-y-auto">
|
||||
<ResizableDialogHeader>
|
||||
<ResizableDialogTitle className="flex items-center gap-2">
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="max-h-[90vh] max-w-7xl overflow-hidden">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Database className="h-5 w-5" />
|
||||
DDL 실행 로그 및 통계
|
||||
</ResizableDialogTitle>
|
||||
</ResizableDialogHeader>
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<Tabs defaultValue="logs" className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-2">
|
||||
|
|
@ -407,7 +407,7 @@ export function DDLLogViewer({ isOpen, onClose }: DDLLogViewerProps) {
|
|||
)}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@ import { Input } from "@/components/ui/input";
|
|||
import { Label } from "@/components/ui/label";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import {
|
||||
ResizableDialog,
|
||||
ResizableDialogContent,
|
||||
ResizableDialogHeader,
|
||||
ResizableDialogTitle,
|
||||
ResizableDialogDescription,
|
||||
ResizableDialogFooter,
|
||||
} from "@/components/ui/resizable-dialog";
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
|
|
@ -266,13 +266,13 @@ export function ExternalCallConfigModal({ isOpen, onClose, onSave, editingConfig
|
|||
};
|
||||
|
||||
return (
|
||||
<ResizableDialog open={isOpen} onOpenChange={onClose}>
|
||||
<ResizableDialogContent className="max-w-[95vw] sm:max-w-2xl">
|
||||
<ResizableDialogHeader>
|
||||
<ResizableDialogTitle className="text-base sm:text-lg">
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="max-w-[95vw] sm:max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-base sm:text-lg">
|
||||
{editingConfig ? "외부 호출 설정 편집" : "새 외부 호출 설정"}
|
||||
</ResizableDialogTitle>
|
||||
</ResizableDialogHeader>
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="max-h-[60vh] space-y-4 overflow-y-auto sm:space-y-6">
|
||||
{/* 기본 정보 */}
|
||||
|
|
@ -564,7 +564,7 @@ export function ExternalCallConfigModal({ isOpen, onClose, onSave, editingConfig
|
|||
)}
|
||||
</div>
|
||||
|
||||
<ResizableDialogFooter className="gap-2 sm:gap-0">
|
||||
<DialogFooter className="gap-2 sm:gap-0">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={onClose}
|
||||
|
|
@ -580,8 +580,8 @@ export function ExternalCallConfigModal({ isOpen, onClose, onSave, editingConfig
|
|||
>
|
||||
{loading ? "저장 중..." : editingConfig ? "수정" : "생성"}
|
||||
</Button>
|
||||
</ResizableDialogFooter>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,13 +8,13 @@ import { Label } from "@/components/ui/label";
|
|||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import {
|
||||
ResizableDialog,
|
||||
ResizableDialogContent,
|
||||
ResizableDialogHeader,
|
||||
ResizableDialogTitle,
|
||||
ResizableDialogDescription,
|
||||
ResizableDialogFooter,
|
||||
} from "@/components/ui/resizable-dialog";
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import {
|
||||
ExternalDbConnectionAPI,
|
||||
|
|
@ -311,13 +311,13 @@ export const ExternalDbConnectionModal: React.FC<ExternalDbConnectionModalProps>
|
|||
};
|
||||
|
||||
return (
|
||||
<ResizableDialog open={isOpen} onOpenChange={onClose}>
|
||||
<ResizableDialogContent className="max-h-[90vh] max-w-[95vw] overflow-y-auto sm:max-w-2xl">
|
||||
<ResizableDialogHeader>
|
||||
<ResizableDialogTitle className="text-base sm:text-lg">
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="max-h-[90vh] max-w-[95vw] overflow-hidden sm:max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-base sm:text-lg">
|
||||
{isEditMode ? "연결 정보 수정" : "새 외부 DB 연결 추가"}
|
||||
</ResizableDialogTitle>
|
||||
</ResizableDialogHeader>
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-3 sm:space-y-4">
|
||||
{/* 기본 정보 */}
|
||||
|
|
@ -607,7 +607,7 @@ export const ExternalDbConnectionModal: React.FC<ExternalDbConnectionModalProps>
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<ResizableDialogFooter className="gap-2 sm:gap-0">
|
||||
<DialogFooter className="gap-2 sm:gap-0">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={onClose}
|
||||
|
|
@ -623,8 +623,8 @@ export const ExternalDbConnectionModal: React.FC<ExternalDbConnectionModalProps>
|
|||
>
|
||||
{loading ? "저장 중..." : isEditMode ? "수정" : "생성"}
|
||||
</Button>
|
||||
</ResizableDialogFooter>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
import { useState, useEffect } from "react";
|
||||
import {
|
||||
ResizableDialog,
|
||||
ResizableDialogContent,
|
||||
ResizableDialogHeader,
|
||||
ResizableDialogTitle,
|
||||
ResizableDialogFooter,
|
||||
} from "@/components/ui/resizable-dialog";
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
|
@ -66,11 +66,11 @@ export default function LangKeyModal({ isOpen, onClose, onSave, keyData, compani
|
|||
};
|
||||
|
||||
return (
|
||||
<ResizableDialog open={isOpen} onOpenChange={handleClose}>
|
||||
<ResizableDialogContent className="sm:max-w-[500px]">
|
||||
<ResizableDialogHeader>
|
||||
<ResizableDialogTitle>{keyData ? "언어 키 수정" : "새 언어 키 추가"}</ResizableDialogTitle>
|
||||
</ResizableDialogHeader>
|
||||
<Dialog open={isOpen} onOpenChange={handleClose}>
|
||||
<DialogContent className="sm:max-w-[500px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{keyData ? "언어 키 수정" : "새 언어 키 추가"}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<Label htmlFor="companyCode">회사</Label>
|
||||
|
|
@ -131,7 +131,7 @@ export default function LangKeyModal({ isOpen, onClose, onSave, keyData, compani
|
|||
<Button type="submit">{keyData ? "수정" : "추가"}</Button>
|
||||
</div>
|
||||
</form>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
import { useState, useEffect } from "react";
|
||||
import {
|
||||
ResizableDialog,
|
||||
ResizableDialogContent,
|
||||
ResizableDialogHeader,
|
||||
ResizableDialogTitle,
|
||||
ResizableDialogFooter,
|
||||
} from "@/components/ui/resizable-dialog";
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
|
@ -68,11 +68,11 @@ export default function LanguageModal({ isOpen, onClose, onSave, languageData }:
|
|||
};
|
||||
|
||||
return (
|
||||
<ResizableDialog open={isOpen} onOpenChange={handleClose}>
|
||||
<ResizableDialogContent className="sm:max-w-[500px]">
|
||||
<ResizableDialogHeader>
|
||||
<ResizableDialogTitle>{languageData ? "언어 수정" : "새 언어 추가"}</ResizableDialogTitle>
|
||||
</ResizableDialogHeader>
|
||||
<Dialog open={isOpen} onOpenChange={handleClose}>
|
||||
<DialogContent className="sm:max-w-[500px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{languageData ? "언어 수정" : "새 언어 추가"}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
|
|
@ -141,8 +141,8 @@ export default function LanguageModal({ isOpen, onClose, onSave, languageData }:
|
|||
<Button type="submit">{languageData ? "수정" : "추가"}</Button>
|
||||
</div>
|
||||
</form>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import {
|
|||
|
||||
DialogHeader,
|
||||
|
||||
} from "@/components/ui/resizable-dialog";
|
||||
} from "@/components/ui/dialog";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
|
|
@ -225,14 +225,14 @@ export const LayoutFormModal: React.FC<LayoutFormModalProps> = ({ open, onOpenCh
|
|||
};
|
||||
|
||||
return (
|
||||
<ResizableDialog open={open} onOpenChange={onOpenChange}>
|
||||
<ResizableDialogContent className="max-h-[90vh] max-w-4xl overflow-y-auto">
|
||||
<ResizableDialogHeader>
|
||||
<ResizableDialogTitle className="flex items-center gap-2">
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-h-[90vh] max-w-4xl overflow-hidden">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Wand2 className="h-5 w-5" />새 레이아웃 생성
|
||||
</ResizableDialogTitle>
|
||||
<ResizableDialogDescription>GUI를 통해 새로운 레이아웃을 쉽게 생성할 수 있습니다.</ResizableDialogDescription>
|
||||
</ResizableDialogHeader>
|
||||
</DialogTitle>
|
||||
<DialogDescription>GUI를 통해 새로운 레이아웃을 쉽게 생성할 수 있습니다.</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
{/* 단계 표시기 */}
|
||||
<div className="mb-6 flex items-center justify-center">
|
||||
|
|
@ -499,7 +499,7 @@ export const LayoutFormModal: React.FC<LayoutFormModalProps> = ({ open, onOpenCh
|
|||
)}
|
||||
</div>
|
||||
|
||||
<ResizableDialogFooter className="gap-2">
|
||||
<DialogFooter className="gap-2">
|
||||
{step !== "basic" && !generationResult && (
|
||||
<Button variant="outline" onClick={handleBack}>
|
||||
이전
|
||||
|
|
@ -527,8 +527,8 @@ export const LayoutFormModal: React.FC<LayoutFormModalProps> = ({ open, onOpenCh
|
|||
<Button variant="outline" onClick={handleClose}>
|
||||
{generationResult?.success ? "완료" : "취소"}
|
||||
</Button>
|
||||
</ResizableDialogFooter>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,11 +9,11 @@ import { Input } from "@/components/ui/input";
|
|||
import { Label } from "@/components/ui/label";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import {
|
||||
ResizableDialog,
|
||||
ResizableDialogContent,
|
||||
ResizableDialogHeader,
|
||||
ResizableDialogTitle
|
||||
} from "@/components/ui/resizable-dialog";
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle
|
||||
} from "@/components/ui/dialog";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import { toast } from "sonner";
|
||||
|
|
@ -684,15 +684,15 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
|
|||
};
|
||||
|
||||
return (
|
||||
<ResizableDialog open={isOpen} onOpenChange={onClose}>
|
||||
<ResizableDialogContent className="sm:max-w-[600px]">
|
||||
<ResizableDialogHeader>
|
||||
<ResizableDialogTitle>
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="sm:max-w-[600px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{isEdit
|
||||
? getText(MENU_MANAGEMENT_KEYS.MODAL_MENU_MODIFY_TITLE)
|
||||
: getText(MENU_MANAGEMENT_KEYS.MODAL_MENU_REGISTER_TITLE)}
|
||||
</ResizableDialogTitle>
|
||||
</ResizableDialogHeader>
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
|
|
@ -1067,7 +1067,7 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
|
|||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,13 +8,13 @@ import { Label } from "@/components/ui/label";
|
|||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import {
|
||||
ResizableDialog,
|
||||
ResizableDialogContent,
|
||||
ResizableDialogHeader,
|
||||
ResizableDialogTitle,
|
||||
ResizableDialogDescription,
|
||||
ResizableDialogFooter,
|
||||
} from "@/components/ui/resizable-dialog";
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import {
|
||||
ExternalRestApiConnectionAPI,
|
||||
|
|
@ -271,11 +271,11 @@ export function RestApiConnectionModal({ isOpen, onClose, onSave, connection }:
|
|||
};
|
||||
|
||||
return (
|
||||
<ResizableDialog open={isOpen} onOpenChange={onClose}>
|
||||
<ResizableDialogContent className="max-h-[90vh] max-w-3xl overflow-y-auto">
|
||||
<ResizableDialogHeader>
|
||||
<ResizableDialogTitle>{connection ? "REST API 연결 수정" : "새 REST API 연결 추가"}</ResizableDialogTitle>
|
||||
</ResizableDialogHeader>
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="max-h-[90vh] max-w-3xl overflow-hidden">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{connection ? "REST API 연결 수정" : "새 REST API 연결 추가"}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-6 py-4">
|
||||
{/* 기본 정보 */}
|
||||
|
|
@ -574,7 +574,7 @@ export function RestApiConnectionModal({ isOpen, onClose, onSave, connection }:
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<ResizableDialogFooter>
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="outline" onClick={onClose}>
|
||||
<X className="mr-2 h-4 w-4" />
|
||||
취소
|
||||
|
|
@ -583,8 +583,8 @@ export function RestApiConnectionModal({ isOpen, onClose, onSave, connection }:
|
|||
<Save className="mr-2 h-4 w-4" />
|
||||
{saving ? "저장 중..." : connection ? "수정" : "생성"}
|
||||
</Button>
|
||||
</ResizableDialogFooter>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
import React, { useState, useCallback } from "react";
|
||||
import {
|
||||
ResizableDialog,
|
||||
ResizableDialogContent,
|
||||
ResizableDialogHeader,
|
||||
ResizableDialogTitle,
|
||||
ResizableDialogDescription,
|
||||
ResizableDialogFooter,
|
||||
} from "@/components/ui/resizable-dialog";
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { roleAPI, RoleGroup } from "@/lib/api/role";
|
||||
import { AlertTriangle } from "lucide-react";
|
||||
|
|
@ -71,11 +71,11 @@ export function RoleDeleteModal({ isOpen, onClose, onSuccess, role }: RoleDelete
|
|||
if (!role) return null;
|
||||
|
||||
return (
|
||||
<ResizableDialog open={isOpen} onOpenChange={onClose}>
|
||||
<ResizableDialogContent className="max-w-[95vw] sm:max-w-[500px]">
|
||||
<ResizableDialogHeader>
|
||||
<ResizableDialogTitle className="text-base sm:text-lg">권한 그룹 삭제</ResizableDialogTitle>
|
||||
</ResizableDialogHeader>
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="max-w-[95vw] sm:max-w-[500px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-base sm:text-lg">권한 그룹 삭제</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* 경고 메시지 */}
|
||||
|
|
@ -133,7 +133,7 @@ export function RoleDeleteModal({ isOpen, onClose, onSuccess, role }: RoleDelete
|
|||
)}
|
||||
</div>
|
||||
|
||||
<ResizableDialogFooter className="gap-2 sm:gap-0">
|
||||
<DialogFooter className="gap-2 sm:gap-0">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={onClose}
|
||||
|
|
@ -150,8 +150,8 @@ export function RoleDeleteModal({ isOpen, onClose, onSuccess, role }: RoleDelete
|
|||
>
|
||||
{isLoading ? "삭제중..." : "삭제"}
|
||||
</Button>
|
||||
</ResizableDialogFooter>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
import React, { useState, useCallback, useEffect, useMemo } from "react";
|
||||
import {
|
||||
ResizableDialog,
|
||||
ResizableDialogContent,
|
||||
ResizableDialogHeader,
|
||||
ResizableDialogTitle,
|
||||
ResizableDialogDescription,
|
||||
ResizableDialogFooter,
|
||||
} from "@/components/ui/resizable-dialog";
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
|
@ -184,11 +184,11 @@ export function RoleFormModal({ isOpen, onClose, onSuccess, editingRole }: RoleF
|
|||
);
|
||||
|
||||
return (
|
||||
<ResizableDialog open={isOpen} onOpenChange={onClose}>
|
||||
<ResizableDialogContent className="max-w-[95vw] sm:max-w-[500px]">
|
||||
<ResizableDialogHeader>
|
||||
<ResizableDialogTitle className="text-base sm:text-lg">{isEditMode ? "권한 그룹 수정" : "권한 그룹 생성"}</ResizableDialogTitle>
|
||||
</ResizableDialogHeader>
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="max-w-[95vw] sm:max-w-[500px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-base sm:text-lg">{isEditMode ? "권한 그룹 수정" : "권한 그룹 생성"}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-3 sm:space-y-4">
|
||||
{/* 권한 그룹명 */}
|
||||
|
|
@ -359,7 +359,7 @@ export function RoleFormModal({ isOpen, onClose, onSuccess, editingRole }: RoleF
|
|||
)}
|
||||
</div>
|
||||
|
||||
<ResizableDialogFooter className="gap-2 sm:gap-0">
|
||||
<DialogFooter className="gap-2 sm:gap-0">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={onClose}
|
||||
|
|
@ -375,8 +375,8 @@ export function RoleFormModal({ isOpen, onClose, onSuccess, editingRole }: RoleF
|
|||
>
|
||||
{isLoading ? "처리중..." : isEditMode ? "수정" : "생성"}
|
||||
</Button>
|
||||
</ResizableDialogFooter>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@
|
|||
import { useState, useEffect, ChangeEvent } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
ResizableDialog,
|
||||
ResizableDialogContent,
|
||||
ResizableDialogHeader,
|
||||
ResizableDialogTitle,
|
||||
ResizableDialogDescription,
|
||||
} from "@/components/ui/resizable-dialog";
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
|
|
@ -179,14 +179,14 @@ export const SqlQueryModal: React.FC<SqlQueryModalProps> = ({ isOpen, onClose, c
|
|||
};
|
||||
|
||||
return (
|
||||
<ResizableDialog open={isOpen} onOpenChange={onClose}>
|
||||
<ResizableDialogContent className="max-h-[90vh] max-w-5xl overflow-y-auto" aria-describedby="modal-description">
|
||||
<ResizableDialogHeader>
|
||||
<ResizableDialogTitle>{connectionName} - SQL 쿼리 실행</ResizableDialogTitle>
|
||||
<ResizableDialogDescription>
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="max-h-[90vh] max-w-5xl overflow-hidden" aria-describedby="modal-description">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{connectionName} - SQL 쿼리 실행</DialogTitle>
|
||||
<DialogDescription>
|
||||
데이터베이스에 대해 SQL SELECT 쿼리를 실행하고 결과를 확인할 수 있습니다.
|
||||
</ResizableDialogDescription>
|
||||
</ResizableDialogHeader>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
{/* 쿼리 입력 영역 */}
|
||||
<div className="space-y-4">
|
||||
|
|
@ -228,7 +228,7 @@ export const SqlQueryModal: React.FC<SqlQueryModalProps> = ({ isOpen, onClose, c
|
|||
<div className="bg-muted/50 space-y-4 rounded-md border p-4">
|
||||
<div>
|
||||
<h3 className="mb-2 text-sm font-medium">사용 가능한 테이블</h3>
|
||||
<div className="max-h-[200px] overflow-y-auto">
|
||||
<div className="max-h-[200px] overflow-hidden">
|
||||
<div className="space-y-2 pr-2">
|
||||
{tables.map((table) => (
|
||||
<div key={table.table_name} className="bg-card rounded-lg border p-3 shadow-sm">
|
||||
|
|
@ -263,7 +263,7 @@ export const SqlQueryModal: React.FC<SqlQueryModalProps> = ({ isOpen, onClose, c
|
|||
{loadingColumns ? (
|
||||
<div className="text-muted-foreground text-sm">컬럼 정보 로딩 중...</div>
|
||||
) : selectedTableColumns.length > 0 ? (
|
||||
<div className="max-h-[200px] overflow-y-auto">
|
||||
<div className="max-h-[200px] overflow-hidden">
|
||||
<div className="bg-card rounded-lg border shadow-sm">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
|
|
@ -332,7 +332,7 @@ export const SqlQueryModal: React.FC<SqlQueryModalProps> = ({ isOpen, onClose, c
|
|||
|
||||
{/* 결과 그리드 */}
|
||||
<div className="bg-card rounded-md border">
|
||||
<div className="max-h-[300px] overflow-y-auto">
|
||||
<div className="max-h-[300px] overflow-hidden">
|
||||
<div className="inline-block min-w-full align-middle">
|
||||
<div className="overflow-x-auto">
|
||||
<Table>
|
||||
|
|
@ -378,7 +378,7 @@ export const SqlQueryModal: React.FC<SqlQueryModalProps> = ({ isOpen, onClose, c
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
import { useState, useEffect } from "react";
|
||||
import {
|
||||
ResizableDialog,
|
||||
ResizableDialogContent,
|
||||
ResizableDialogHeader,
|
||||
ResizableDialogTitle,
|
||||
ResizableDialogFooter
|
||||
} from "@/components/ui/resizable-dialog";
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogFooter
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
|
|
@ -126,14 +126,14 @@ export function TableLogViewer({ tableName, open, onOpenChange }: TableLogViewer
|
|||
};
|
||||
|
||||
return (
|
||||
<ResizableDialog open={open} onOpenChange={onOpenChange}>
|
||||
<ResizableDialogContent className="flex max-h-[90vh] max-w-6xl flex-col overflow-hidden">
|
||||
<ResizableDialogHeader>
|
||||
<ResizableDialogTitle className="flex items-center gap-2">
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="flex max-h-[90vh] max-w-6xl flex-col overflow-hidden">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<History className="h-5 w-5" />
|
||||
{tableName} - 변경 이력
|
||||
</ResizableDialogTitle>
|
||||
</ResizableDialogHeader>
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
{/* 필터 영역 */}
|
||||
<div className="space-y-3 rounded-lg border p-4">
|
||||
|
|
@ -261,7 +261,7 @@ export function TableLogViewer({ tableName, open, onOpenChange }: TableLogViewer
|
|||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { Label } from "@/components/ui/label";
|
|||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
import { ResizableDialog, ResizableDialogContent, ResizableDialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
||||
import { Upload, Download, FileText, AlertCircle, CheckCircle } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { useTemplates } from "@/hooks/admin/useTemplates";
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import React, { useState, useCallback, useEffect, useMemo } from "react";
|
||||
import { ResizableDialog, ResizableDialogContent, ResizableDialogHeader, ResizableDialogTitle } from "@/components/ui/resizable-dialog";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
|
@ -32,11 +32,11 @@ function AlertModal({ isOpen, onClose, title, message, type = "info" }: AlertMod
|
|||
};
|
||||
|
||||
return (
|
||||
<ResizableDialog open={isOpen} onOpenChange={onClose}>
|
||||
<ResizableDialogContent className="sm:max-w-md">
|
||||
<ResizableDialogHeader>
|
||||
<ResizableDialogTitle className={getTypeColor()}>{title}</ResizableDialogTitle>
|
||||
</ResizableDialogHeader>
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle className={getTypeColor()}>{title}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="py-4">
|
||||
<p className="text-muted-foreground text-sm">{message}</p>
|
||||
</div>
|
||||
|
|
@ -45,8 +45,8 @@ function AlertModal({ isOpen, onClose, title, message, type = "info" }: AlertMod
|
|||
확인
|
||||
</Button>
|
||||
</div>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -441,11 +441,11 @@ export function UserFormModal({ isOpen, onClose, onSuccess, editingUser }: UserF
|
|||
|
||||
return (
|
||||
<>
|
||||
<ResizableDialog open={isOpen} onOpenChange={handleClose}>
|
||||
<ResizableDialogContent className="max-h-[90vh] max-w-2xl overflow-y-auto">
|
||||
<ResizableDialogHeader>
|
||||
<ResizableDialogTitle>{isEditMode ? "사용자 정보 수정" : "사용자 등록"}</ResizableDialogTitle>
|
||||
</ResizableDialogHeader>
|
||||
<Dialog open={isOpen} onOpenChange={handleClose}>
|
||||
<DialogContent className="max-h-[90vh] max-w-2xl overflow-hidden">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{isEditMode ? "사용자 정보 수정" : "사용자 등록"}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-6 py-4">
|
||||
{/* 기본 정보 */}
|
||||
|
|
@ -684,8 +684,8 @@ export function UserFormModal({ isOpen, onClose, onSuccess, editingUser }: UserF
|
|||
{isLoading ? "처리중..." : isEditMode ? "수정" : "등록"}
|
||||
</Button>
|
||||
</div>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* 알림 모달 */}
|
||||
<AlertModal
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import {
|
||||
ResizableDialog,
|
||||
ResizableDialogContent,
|
||||
ResizableDialogHeader,
|
||||
ResizableDialogTitle,
|
||||
} from "@/components/ui/resizable-dialog";
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
|
@ -152,17 +152,17 @@ export function UserHistoryModal({ isOpen, onClose, userId, userName }: UserHist
|
|||
};
|
||||
|
||||
return (
|
||||
<ResizableDialog open={isOpen} onOpenChange={onClose}>
|
||||
<ResizableDialogContent className="flex max-h-[90vh] max-w-6xl flex-col">
|
||||
<ResizableDialogHeader className="flex-shrink-0">
|
||||
<ResizableDialogTitle className="flex items-center gap-2">
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="flex max-h-[90vh] max-w-6xl flex-col">
|
||||
<DialogHeader className="flex-shrink-0">
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<User className="h-5 w-5" />
|
||||
사용자 관리 이력
|
||||
</ResizableDialogTitle>
|
||||
</DialogTitle>
|
||||
<div className="text-muted-foreground text-sm">
|
||||
{userName} ({userId})의 변경이력을 조회합니다.
|
||||
</div>
|
||||
</ResizableDialogHeader>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="flex min-h-0 flex-1 flex-col">
|
||||
{/* 로딩 상태 */}
|
||||
|
|
@ -254,7 +254,7 @@ export function UserHistoryModal({ isOpen, onClose, userId, userName }: UserHist
|
|||
닫기
|
||||
</Button>
|
||||
</div>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import React, { useState, useCallback } from "react";
|
||||
import { ResizableDialog, ResizableDialogContent, ResizableDialogHeader, DialogTitle } from "@/components/ui/resizable-dialog";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
|
@ -127,11 +127,11 @@ export function UserPasswordResetModal({ isOpen, onClose, userId, userName, onSu
|
|||
if (!userId) return null;
|
||||
|
||||
return (
|
||||
<ResizableDialog open={isOpen} onOpenChange={handleClose}>
|
||||
<ResizableDialogContent className="sm:max-w-md">
|
||||
<ResizableDialogHeader>
|
||||
<ResizableDialogTitle>비밀번호 초기화</ResizableDialogTitle>
|
||||
</ResizableDialogHeader>
|
||||
<Dialog open={isOpen} onOpenChange={handleClose}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>비밀번호 초기화</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4" onKeyDown={handleKeyDown}>
|
||||
{/* 대상 사용자 정보 */}
|
||||
|
|
@ -215,7 +215,7 @@ export function UserPasswordResetModal({ isOpen, onClose, userId, userName, onSu
|
|||
{isLoading ? "처리중..." : "초기화"}
|
||||
</Button>
|
||||
</div>
|
||||
</ResizableDialogContent>
|
||||
</DialogContent>
|
||||
|
||||
{/* 알림 모달 */}
|
||||
<AlertModal
|
||||
|
|
@ -225,6 +225,6 @@ export function UserPasswordResetModal({ isOpen, onClose, userId, userName, onSu
|
|||
title={alertState.title}
|
||||
message={alertState.message}
|
||||
/>
|
||||
</ResizableDialog>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,12 +13,12 @@ import { DashboardProvider } from "@/contexts/DashboardContext";
|
|||
import { useMenu } from "@/contexts/MenuContext";
|
||||
import { useKeyboardShortcuts } from "./hooks/useKeyboardShortcuts";
|
||||
import {
|
||||
ResizableDialog,
|
||||
ResizableDialogContent,
|
||||
ResizableDialogDescription,
|
||||
ResizableDialogHeader,
|
||||
ResizableDialogTitle,
|
||||
} from "@/components/ui/resizable-dialog";
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
|
|
@ -639,23 +639,23 @@ export default function DashboardDesigner({ dashboardId: initialDashboardId }: D
|
|||
/>
|
||||
|
||||
{/* 저장 성공 모달 */}
|
||||
<ResizableDialog
|
||||
<Dialog
|
||||
open={successModalOpen}
|
||||
onOpenChange={() => {
|
||||
setSuccessModalOpen(false);
|
||||
router.push("/admin/dashboard");
|
||||
}}
|
||||
>
|
||||
<ResizableDialogContent className="sm:max-w-md">
|
||||
<ResizableDialogHeader>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<div className="bg-success/10 mx-auto flex h-12 w-12 items-center justify-center rounded-full">
|
||||
<CheckCircle2 className="text-success h-6 w-6" />
|
||||
</div>
|
||||
<ResizableDialogTitle className="text-center">저장 완료</ResizableDialogTitle>
|
||||
<ResizableDialogDescription className="text-center">
|
||||
<DialogTitle className="text-center">저장 완료</DialogTitle>
|
||||
<DialogDescription className="text-center">
|
||||
대시보드가 성공적으로 저장되었습니다.
|
||||
</ResizableDialogDescription>
|
||||
</ResizableDialogHeader>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="flex justify-center pt-4">
|
||||
<Button
|
||||
onClick={() => {
|
||||
|
|
@ -666,8 +666,8 @@ export default function DashboardDesigner({ dashboardId: initialDashboardId }: D
|
|||
확인
|
||||
</Button>
|
||||
</div>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* 초기화 확인 모달 */}
|
||||
<AlertDialog open={clearConfirmOpen} onOpenChange={setClearConfirmOpen}>
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
import { useState, useEffect } from "react";
|
||||
import {
|
||||
ResizableDialog,
|
||||
ResizableDialogContent,
|
||||
ResizableDialogHeader,
|
||||
ResizableDialogTitle,
|
||||
ResizableDialogDescription,
|
||||
ResizableDialogFooter,
|
||||
} from "@/components/ui/resizable-dialog";
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
|
@ -174,11 +174,11 @@ export function DashboardSaveModal({
|
|||
const flatMenus = flattenMenus(currentMenus);
|
||||
|
||||
return (
|
||||
<ResizableDialog open={isOpen} onOpenChange={onClose}>
|
||||
<ResizableDialogContent className="max-h-[90vh] max-w-2xl overflow-y-auto">
|
||||
<ResizableDialogHeader>
|
||||
<ResizableDialogTitle>{isEditing ? "대시보드 수정" : "대시보드 저장"}</ResizableDialogTitle>
|
||||
</ResizableDialogHeader>
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="max-h-[90vh] max-w-2xl overflow-hidden">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{isEditing ? "대시보드 수정" : "대시보드 저장"}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-6 py-4">
|
||||
{/* 대시보드 이름 */}
|
||||
|
|
@ -312,7 +312,7 @@ export function DashboardSaveModal({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<ResizableDialogFooter>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={onClose} disabled={loading}>
|
||||
취소
|
||||
</Button>
|
||||
|
|
@ -329,8 +329,8 @@ export function DashboardSaveModal({
|
|||
</>
|
||||
)}
|
||||
</Button>
|
||||
</ResizableDialogFooter>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
ResizableDialog,
|
||||
ResizableDialogContent,
|
||||
ResizableDialogHeader,
|
||||
ResizableDialogTitle,
|
||||
ResizableDialogDescription,
|
||||
ResizableDialogFooter,
|
||||
} from "@/components/ui/resizable-dialog";
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
|
|
@ -116,14 +116,14 @@ export const MenuAssignmentModal: React.FC<MenuAssignmentModalProps> = ({
|
|||
};
|
||||
|
||||
return (
|
||||
<ResizableDialog open={isOpen} onOpenChange={handleClose}>
|
||||
<ResizableDialogContent className="sm:max-w-[500px]">
|
||||
<ResizableDialogHeader>
|
||||
<Dialog open={isOpen} onOpenChange={handleClose}>
|
||||
<DialogContent className="sm:max-w-[500px]">
|
||||
<DialogHeader>
|
||||
<div className="flex items-center gap-2">
|
||||
<ResizableDialogTitle>대시보드 저장 완료</ResizableDialogTitle>
|
||||
<ResizableDialogDescription>'{dashboardTitle}' 대시보드가 저장되었습니다.</ResizableDialogDescription>
|
||||
<DialogTitle>대시보드 저장 완료</DialogTitle>
|
||||
<DialogDescription>'{dashboardTitle}' 대시보드가 저장되었습니다.</DialogDescription>
|
||||
</div>
|
||||
</ResizableDialogHeader>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="space-y-3">
|
||||
|
|
@ -200,13 +200,13 @@ export const MenuAssignmentModal: React.FC<MenuAssignmentModalProps> = ({
|
|||
)}
|
||||
</div>
|
||||
|
||||
<ResizableDialogFooter>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={handleClose}>
|
||||
취소
|
||||
</Button>
|
||||
<Button onClick={handleConfirm}>{assignToMenu ? "메뉴에 할당하고 완료" : "완료"}</Button>
|
||||
</ResizableDialogFooter>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@
|
|||
import { useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
ResizableDialog,
|
||||
ResizableDialogContent,
|
||||
ResizableDialogHeader,
|
||||
ResizableDialogTitle,
|
||||
ResizableDialogDescription,
|
||||
ResizableDialogFooter,
|
||||
} from "@/components/ui/resizable-dialog";
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Loader2 } from "lucide-react";
|
||||
|
|
@ -94,10 +94,10 @@ export default function MaterialAddModal({ isOpen, material, onClose, onAdd }: M
|
|||
if (!open) onClose();
|
||||
}}
|
||||
>
|
||||
<ResizableDialogContent className="max-w-2xl">
|
||||
<ResizableDialogHeader>
|
||||
<ResizableDialogTitle>자재 배치 설정</ResizableDialogTitle>
|
||||
</ResizableDialogHeader>
|
||||
<DialogContent className="max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>자재 배치 설정</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* 자재 정보 */}
|
||||
|
|
@ -233,7 +233,7 @@ export default function MaterialAddModal({ isOpen, material, onClose, onAdd }: M
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<ResizableDialogFooter>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={onClose} disabled={isAdding}>
|
||||
취소
|
||||
</Button>
|
||||
|
|
@ -247,8 +247,8 @@ export default function MaterialAddModal({ isOpen, material, onClose, onAdd }: M
|
|||
"배치"
|
||||
)}
|
||||
</Button>
|
||||
</ResizableDialogFooter>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import { useState, useEffect } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { ResizableDialog, ResizableDialogContent, ResizableDialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||
import { Search, Loader2 } from "lucide-react";
|
||||
import { materialApi } from "@/lib/api/yardLayoutApi";
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@
|
|||
import { useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
ResizableDialog,
|
||||
ResizableDialogContent,
|
||||
ResizableDialogHeader,
|
||||
ResizableDialogTitle,
|
||||
ResizableDialogDescription,
|
||||
ResizableDialogFooter,
|
||||
} from "@/components/ui/resizable-dialog";
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
|
|
@ -64,14 +64,14 @@ export default function YardLayoutCreateModal({ isOpen, onClose, onCreate }: Yar
|
|||
};
|
||||
|
||||
return (
|
||||
<ResizableDialog open={isOpen} onOpenChange={handleClose}>
|
||||
<ResizableDialogContent className="sm:max-w-[500px]" onPointerDown={(e) => e.stopPropagation()}>
|
||||
<ResizableDialogHeader>
|
||||
<Dialog open={isOpen} onOpenChange={handleClose}>
|
||||
<DialogContent className="sm:max-w-[500px]" onPointerDown={(e) => e.stopPropagation()}>
|
||||
<DialogHeader>
|
||||
<div className="flex items-center gap-2">
|
||||
<ResizableDialogTitle>새로운 3D필드 생성</ResizableDialogTitle>
|
||||
<ResizableDialogDescription>필드 이름을 입력하세요</ResizableDialogDescription>
|
||||
<DialogTitle>새로운 3D필드 생성</DialogTitle>
|
||||
<DialogDescription>필드 이름을 입력하세요</DialogDescription>
|
||||
</div>
|
||||
</ResizableDialogHeader>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="space-y-2">
|
||||
|
|
@ -100,7 +100,7 @@ export default function YardLayoutCreateModal({ isOpen, onClose, onCreate }: Yar
|
|||
)}
|
||||
</div>
|
||||
|
||||
<ResizableDialogFooter>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={handleClose} disabled={isCreating}>
|
||||
취소
|
||||
</Button>
|
||||
|
|
@ -114,8 +114,8 @@ export default function YardLayoutCreateModal({ isOpen, onClose, onCreate }: Yar
|
|||
"생성"
|
||||
)}
|
||||
</Button>
|
||||
</ResizableDialogFooter>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import {
|
||||
ResizableDialog,
|
||||
ResizableDialogContent,
|
||||
ResizableDialogHeader,
|
||||
ResizableDialogTitle,
|
||||
ResizableDialogDescription,
|
||||
ResizableDialogFooter,
|
||||
} from "@/components/ui/resizable-dialog";
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { toast } from "sonner";
|
||||
import { Camera, CameraOff, CheckCircle2, AlertCircle, Scan } from "lucide-react";
|
||||
|
|
@ -179,26 +179,15 @@ export const BarcodeScanModal: React.FC<BarcodeScanModalProps> = ({
|
|||
};
|
||||
|
||||
return (
|
||||
<ResizableDialog open={open} onOpenChange={onOpenChange}>
|
||||
<ResizableDialogContent
|
||||
className="max-w-[95vw] sm:max-w-[600px]"
|
||||
defaultWidth={600}
|
||||
defaultHeight={700}
|
||||
minWidth={400}
|
||||
minHeight={500}
|
||||
maxWidth={900}
|
||||
maxHeight={900}
|
||||
modalId="barcode-scan"
|
||||
userId={userId}
|
||||
>
|
||||
<ResizableDialogHeader>
|
||||
<ResizableDialogTitle className="text-base sm:text-lg">바코드 스캔</ResizableDialogTitle>
|
||||
<ResizableDialogDescription className="text-xs sm:text-sm">
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-[95vw] sm:max-w-[600px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-base sm:text-lg">바코드 스캔</DialogTitle>
|
||||
<DialogDescription className="text-xs sm:text-sm">
|
||||
카메라로 바코드를 스캔하세요.
|
||||
{targetField && ` (대상 필드: ${targetField})`}
|
||||
모달 테두리를 드래그하여 크기를 조절할 수 있습니다.
|
||||
</ResizableDialogDescription>
|
||||
</ResizableDialogHeader>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* 카메라 권한 요청 대기 중 */}
|
||||
|
|
@ -337,7 +326,7 @@ export const BarcodeScanModal: React.FC<BarcodeScanModalProps> = ({
|
|||
)}
|
||||
</div>
|
||||
|
||||
<ResizableDialogFooter className="gap-2 sm:gap-0">
|
||||
<DialogFooter className="gap-2 sm:gap-0">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => onOpenChange(false)}
|
||||
|
|
@ -376,9 +365,9 @@ export const BarcodeScanModal: React.FC<BarcodeScanModalProps> = ({
|
|||
확인
|
||||
</Button>
|
||||
)}
|
||||
</ResizableDialogFooter>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import {
|
||||
ResizableDialog,
|
||||
ResizableDialogContent,
|
||||
ResizableDialogHeader,
|
||||
ResizableDialogTitle,
|
||||
ResizableDialogDescription,
|
||||
ResizableDialogFooter,
|
||||
} from "@/components/ui/resizable-dialog";
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
|
|
@ -385,27 +385,27 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
|
|||
}, [open]);
|
||||
|
||||
return (
|
||||
<ResizableDialog open={open} onOpenChange={onOpenChange}>
|
||||
<ResizableDialogContent
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent
|
||||
className="max-h-[95vh] max-w-[95vw] sm:max-w-[1200px]"
|
||||
defaultWidth={1000}
|
||||
defaultHeight={700}
|
||||
minWidth={700}
|
||||
minHeight={500}
|
||||
maxWidth={1400}
|
||||
maxHeight={900}
|
||||
modalId={`excel-upload-${tableName}`}
|
||||
userId={userId || "guest"}
|
||||
style={{
|
||||
width: "1000px",
|
||||
height: "700px",
|
||||
minWidth: "700px",
|
||||
minHeight: "500px",
|
||||
maxWidth: "1400px",
|
||||
maxHeight: "900px",
|
||||
}}
|
||||
>
|
||||
<ResizableDialogHeader>
|
||||
<ResizableDialogTitle className="flex items-center gap-2 text-base sm:text-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2 text-base sm:text-lg">
|
||||
<FileSpreadsheet className="h-5 w-5" />
|
||||
엑셀 데이터 업로드
|
||||
</ResizableDialogTitle>
|
||||
<ResizableDialogDescription className="text-xs sm:text-sm">
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-xs sm:text-sm">
|
||||
엑셀 파일을 선택하고 컬럼을 매핑하여 데이터를 업로드하세요. 모달 테두리를 드래그하여 크기를 조절할 수 있습니다.
|
||||
</ResizableDialogDescription>
|
||||
</ResizableDialogHeader>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
{/* 스텝 인디케이터 */}
|
||||
<div className="flex items-center justify-between">
|
||||
|
|
@ -863,7 +863,7 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
|
|||
)}
|
||||
</div>
|
||||
|
||||
<ResizableDialogFooter className="gap-2 sm:gap-0">
|
||||
<DialogFooter className="gap-2 sm:gap-0">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={currentStep === 1 ? () => onOpenChange(false) : handlePrevious}
|
||||
|
|
@ -889,8 +889,8 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
|
|||
{isUploading ? "업로드 중..." : "다음"}
|
||||
</Button>
|
||||
)}
|
||||
</ResizableDialogFooter>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import {
|
||||
ResizableDialog,
|
||||
ResizableDialogContent,
|
||||
ResizableDialogHeader,
|
||||
ResizableDialogTitle,
|
||||
ResizableDialogDescription,
|
||||
} from "@/components/ui/resizable-dialog";
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { InteractiveScreenViewerDynamic } from "@/components/screen/InteractiveScreenViewerDynamic";
|
||||
|
|
@ -514,16 +514,18 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
|
|||
}
|
||||
|
||||
// 화면관리에서 설정한 크기 = 컨텐츠 영역 크기
|
||||
// 실제 모달 크기 = 컨텐츠 + 헤더 + 연속등록 체크박스
|
||||
const headerHeight = 60; // DialogHeader (타이틀 + 패딩)
|
||||
// 실제 모달 크기 = 컨텐츠 + 헤더 + 연속등록 체크박스 + gap + padding
|
||||
const headerHeight = 52; // DialogHeader (타이틀 + border-b + py-3)
|
||||
const footerHeight = 52; // 연속 등록 모드 체크박스 영역
|
||||
const dialogGap = 16; // DialogContent gap-4
|
||||
const extraPadding = 24; // 추가 여백 (안전 마진)
|
||||
|
||||
const totalHeight = screenDimensions.height + headerHeight + footerHeight;
|
||||
const totalHeight = screenDimensions.height + headerHeight + footerHeight + dialogGap + extraPadding;
|
||||
|
||||
return {
|
||||
className: "overflow-hidden p-0",
|
||||
style: {
|
||||
width: `${Math.min(screenDimensions.width, window.innerWidth * 0.98)}px`,
|
||||
width: `${Math.min(screenDimensions.width + 48, window.innerWidth * 0.98)}px`, // 좌우 패딩 추가
|
||||
height: `${Math.min(totalHeight, window.innerHeight * 0.95)}px`,
|
||||
maxWidth: "98vw",
|
||||
maxHeight: "95vh",
|
||||
|
|
@ -593,36 +595,28 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
|
|||
]);
|
||||
|
||||
return (
|
||||
<ResizableDialog open={modalState.isOpen} onOpenChange={handleClose}>
|
||||
<ResizableDialogContent
|
||||
className={`${modalStyle.className} ${className || ""}`}
|
||||
{...(modalStyle.style && { style: modalStyle.style })} // undefined일 때는 prop 자체를 전달하지 않음
|
||||
defaultWidth={600}
|
||||
defaultHeight={800}
|
||||
minWidth={500}
|
||||
minHeight={400}
|
||||
maxWidth={1600}
|
||||
maxHeight={1200}
|
||||
modalId={persistedModalId}
|
||||
userId={userId || "guest"}
|
||||
<Dialog open={modalState.isOpen} onOpenChange={handleClose}>
|
||||
<DialogContent
|
||||
className={`${modalStyle.className} ${className || ""} max-w-none`}
|
||||
{...(modalStyle.style && { style: modalStyle.style })}
|
||||
>
|
||||
<ResizableDialogHeader className="shrink-0 border-b px-4 py-3">
|
||||
<DialogHeader className="shrink-0 border-b px-4 py-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<ResizableDialogTitle className="text-base">{modalState.title}</ResizableDialogTitle>
|
||||
<DialogTitle className="text-base">{modalState.title}</DialogTitle>
|
||||
{modalState.description && !loading && (
|
||||
<ResizableDialogDescription className="text-muted-foreground text-xs">
|
||||
<DialogDescription className="text-muted-foreground text-xs">
|
||||
{modalState.description}
|
||||
</ResizableDialogDescription>
|
||||
</DialogDescription>
|
||||
)}
|
||||
{loading && (
|
||||
<ResizableDialogDescription className="text-xs">
|
||||
<DialogDescription className="text-xs">
|
||||
{loading ? "화면을 불러오는 중입니다..." : ""}
|
||||
</ResizableDialogDescription>
|
||||
</DialogDescription>
|
||||
)}
|
||||
</div>
|
||||
</ResizableDialogHeader>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="flex-1 overflow-auto">
|
||||
<div className="flex-1 overflow-y-auto [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-thumb]:bg-gray-300 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-track]:bg-transparent">
|
||||
{loading ? (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<div className="text-center">
|
||||
|
|
@ -728,8 +722,8 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
|
|||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@
|
|||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
ResizableDialog,
|
||||
ResizableDialogContent,
|
||||
ResizableDialogHeader,
|
||||
ResizableDialogTitle,
|
||||
ResizableDialogDescription,
|
||||
} from "@/components/ui/resizable-dialog";
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
|
|
@ -209,7 +209,7 @@ export function TableHistoryModal({
|
|||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-h-[90vh] max-w-[95vw] sm:max-w-[900px]">
|
||||
<DialogHeader>
|
||||
<ResizableDialogTitle className="flex items-center gap-2 text-base sm:text-lg">
|
||||
<DialogTitle className="flex items-center gap-2 text-base sm:text-lg">
|
||||
<Clock className="h-5 w-5" />
|
||||
변경 이력{" "}
|
||||
{!recordId && (
|
||||
|
|
@ -217,12 +217,12 @@ export function TableHistoryModal({
|
|||
전체
|
||||
</Badge>
|
||||
)}
|
||||
</ResizableDialogTitle>
|
||||
<ResizableDialogDescription className="text-xs sm:text-sm">
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-xs sm:text-sm">
|
||||
{recordId
|
||||
? `${recordDisplayValue || recordLabel || "-"} - ${tableName} 테이블`
|
||||
: `${tableName} 테이블 전체 이력`}
|
||||
</ResizableDialogDescription>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
{loading ? (
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
ResizableDialog,
|
||||
ResizableDialogContent,
|
||||
ResizableDialogHeader,
|
||||
ResizableDialogTitle,
|
||||
ResizableDialogDescription,
|
||||
ResizableDialogFooter,
|
||||
} from "@/components/ui/resizable-dialog";
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
|
|
@ -150,23 +150,14 @@ export function TableOptionsModal({
|
|||
};
|
||||
|
||||
return (
|
||||
<ResizableDialog open={isOpen} onOpenChange={onClose}>
|
||||
<ResizableDialogContent
|
||||
defaultWidth={700}
|
||||
defaultHeight={600}
|
||||
minWidth={500}
|
||||
minHeight={400}
|
||||
maxWidth={1200}
|
||||
maxHeight={900}
|
||||
modalId={`table-options-${tableName}`}
|
||||
userId={userId}
|
||||
>
|
||||
<ResizableDialogHeader>
|
||||
<ResizableDialogTitle className="text-base sm:text-lg">테이블 옵션</ResizableDialogTitle>
|
||||
<ResizableDialogDescription className="text-xs sm:text-sm">
|
||||
컬럼 표시/숨기기, 순서 변경, 틀고정 등을 설정할 수 있습니다. 모달 테두리를 드래그하여 크기를 조절할 수 있습니다.
|
||||
</ResizableDialogDescription>
|
||||
</ResizableDialogHeader>
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="max-h-[90vh] sm:max-w-[700px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-base sm:text-lg">테이블 옵션</DialogTitle>
|
||||
<DialogDescription className="text-xs sm:text-sm">
|
||||
컬럼 표시/숨기기, 순서 변경, 틀고정 등을 설정할 수 있습니다.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<Tabs defaultValue="columns" className="flex flex-col flex-1 overflow-hidden">
|
||||
<TabsList className="grid w-full grid-cols-3 flex-shrink-0">
|
||||
|
|
@ -303,7 +294,7 @@ export function TableOptionsModal({
|
|||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
<ResizableDialogFooter className="gap-2 sm:gap-0 mt-4">
|
||||
<DialogFooter className="gap-2 sm:gap-0 mt-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleReset}
|
||||
|
|
@ -324,9 +315,9 @@ export function TableOptionsModal({
|
|||
>
|
||||
저장
|
||||
</Button>
|
||||
</ResizableDialogFooter>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import {
|
||||
ResizableDialog,
|
||||
ResizableDialogContent,
|
||||
ResizableDialogHeader,
|
||||
ResizableDialogTitle,
|
||||
ResizableDialogDescription,
|
||||
ResizableDialogFooter,
|
||||
} from "@/components/ui/resizable-dialog";
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
|
|
@ -673,14 +673,14 @@ export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
|
|||
|
||||
return (
|
||||
<>
|
||||
<ResizableDialog open={isOpen} onOpenChange={handleCancel}>
|
||||
<ResizableDialogContent className="max-h-[80vh] max-w-3xl overflow-y-auto">
|
||||
<ResizableDialogHeader>
|
||||
<ResizableDialogTitle className="flex items-center gap-2 text-lg">
|
||||
<Dialog open={isOpen} onOpenChange={handleCancel}>
|
||||
<DialogContent className="max-h-[80vh] max-w-3xl overflow-hidden">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2 text-lg">
|
||||
<Link className="h-4 w-4" />
|
||||
필드 연결 설정
|
||||
</ResizableDialogTitle>
|
||||
</ResizableDialogHeader>
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* 기본 연결 설정 */}
|
||||
|
|
@ -719,16 +719,16 @@ export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
|
|||
{renderConnectionTypeSettings()}
|
||||
</div>
|
||||
|
||||
<ResizableDialogFooter>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={handleCancel}>
|
||||
취소
|
||||
</Button>
|
||||
<Button onClick={handleConfirm} disabled={isButtonDisabled()}>
|
||||
연결 생성
|
||||
</Button>
|
||||
</ResizableDialogFooter>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<AlertDialog open={showSuccessModal} onOpenChange={setShowSuccessModal}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
ResizableDialog,
|
||||
ResizableDialogContent,
|
||||
ResizableDialogHeader,
|
||||
ResizableDialogTitle,
|
||||
ResizableDialogDescription,
|
||||
ResizableDialogFooter,
|
||||
} from "@/components/ui/resizable-dialog";
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
|
|
@ -133,11 +133,11 @@ const SaveDiagramModal: React.FC<SaveDiagramModalProps> = ({
|
|||
|
||||
return (
|
||||
<>
|
||||
<ResizableDialog open={isOpen} onOpenChange={handleClose}>
|
||||
<ResizableDialogContent className="max-h-[80vh] max-w-2xl overflow-y-auto">
|
||||
<ResizableDialogHeader>
|
||||
<ResizableDialogTitle className="text-lg font-semibold">📊 관계도 저장</ResizableDialogTitle>
|
||||
</ResizableDialogHeader>
|
||||
<Dialog open={isOpen} onOpenChange={handleClose}>
|
||||
<DialogContent className="max-h-[80vh] max-w-2xl overflow-hidden">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-lg font-semibold">📊 관계도 저장</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* 관계도 이름 입력 */}
|
||||
|
|
@ -203,7 +203,7 @@ const SaveDiagramModal: React.FC<SaveDiagramModalProps> = ({
|
|||
<CardTitle className="text-sm">관계 목록</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="max-h-60 space-y-3 overflow-y-auto">
|
||||
<div className="max-h-60 space-y-3 overflow-hidden">
|
||||
{relationships.map((relationship, index) => (
|
||||
<div
|
||||
key={relationship.id || index}
|
||||
|
|
@ -242,7 +242,7 @@ const SaveDiagramModal: React.FC<SaveDiagramModalProps> = ({
|
|||
)}
|
||||
</div>
|
||||
|
||||
<ResizableDialogFooter className="flex gap-2">
|
||||
<DialogFooter className="flex gap-2">
|
||||
<Button variant="outline" onClick={handleClose} disabled={isLoading}>
|
||||
취소
|
||||
</Button>
|
||||
|
|
@ -260,9 +260,9 @@ const SaveDiagramModal: React.FC<SaveDiagramModalProps> = ({
|
|||
"저장하기"
|
||||
)}
|
||||
</Button>
|
||||
</ResizableDialogFooter>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* 저장 성공 알림 모달 */}
|
||||
<AlertDialog open={showSuccessModal} onOpenChange={setShowSuccessModal}>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { useEffect, useState } from "react";
|
||||
import { Loader2, FileJson, Calendar, Trash2 } from "lucide-react";
|
||||
import { ResizableDialog, ResizableDialogContent, ResizableDialogDescription, ResizableDialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { getNodeFlows, deleteNodeFlow } from "@/lib/api/nodeFlows";
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { ResizableDialog, ResizableDialogContent, ResizableDialogHeader, DialogDescription } from "@/components/ui/resizable-dialog";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogDescription } from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from "@/components/ui/table";
|
||||
|
|
@ -130,11 +130,11 @@ export function FlowDataListModal({
|
|||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="flex max-h-[80vh] max-w-4xl flex-col overflow-hidden">
|
||||
<DialogHeader>
|
||||
<ResizableDialogTitle className="flex items-center gap-2">
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
{stepName}
|
||||
<Badge variant="secondary">{data.length}건</Badge>
|
||||
</ResizableDialogTitle>
|
||||
<DialogDescription>이 단계에 해당하는 데이터 목록입니다</ResizableDialogDescription>
|
||||
</DialogTitle>
|
||||
<DialogDescription>이 단계에 해당하는 데이터 목록입니다</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="flex-1 overflow-auto">
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import {
|
||||
ResizableDialog,
|
||||
ResizableDialogContent,
|
||||
ResizableDialogHeader,
|
||||
ResizableDialogTitle,
|
||||
ResizableDialogDescription,
|
||||
ResizableDialogFooter,
|
||||
} from "@/components/ui/resizable-dialog";
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
|
@ -48,11 +48,11 @@ function AlertModal({ isOpen, onClose, title, message, type = "info" }: AlertMod
|
|||
};
|
||||
|
||||
return (
|
||||
<ResizableDialog open={isOpen} onOpenChange={onClose}>
|
||||
<ResizableDialogContent className="sm:max-w-md">
|
||||
<ResizableDialogHeader>
|
||||
<ResizableDialogTitle className={getTypeColor()}>{title}</ResizableDialogTitle>
|
||||
</ResizableDialogHeader>
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle className={getTypeColor()}>{title}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="py-4">
|
||||
<p className="text-sm text-muted-foreground">{message}</p>
|
||||
</div>
|
||||
|
|
@ -61,8 +61,8 @@ function AlertModal({ isOpen, onClose, title, message, type = "info" }: AlertMod
|
|||
확인
|
||||
</Button>
|
||||
</div>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -165,11 +165,11 @@ export function ProfileModal({
|
|||
};
|
||||
return (
|
||||
<>
|
||||
<ResizableDialog open={isOpen} onOpenChange={onClose}>
|
||||
<ResizableDialogContent className="sm:max-w-[500px]">
|
||||
<ResizableDialogHeader>
|
||||
<ResizableDialogTitle>프로필 수정</ResizableDialogTitle>
|
||||
</ResizableDialogHeader>
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="sm:max-w-[500px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>프로필 수정</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="grid gap-6 py-4">
|
||||
{/* 프로필 사진 섹션 */}
|
||||
|
|
@ -449,16 +449,16 @@ export function ProfileModal({
|
|||
)}
|
||||
</div>
|
||||
|
||||
<ResizableDialogFooter>
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="outline" onClick={onClose} disabled={isSaving}>
|
||||
취소
|
||||
</Button>
|
||||
<Button type="button" onClick={onSave} disabled={isSaving}>
|
||||
{isSaving ? "저장 중..." : "저장"}
|
||||
</Button>
|
||||
</ResizableDialogFooter>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* 알림 모달 */}
|
||||
<AlertModal
|
||||
|
|
@ -471,14 +471,14 @@ export function ProfileModal({
|
|||
|
||||
{/* 새 차량 등록 모달 */}
|
||||
{isVehicleRegisterModalOpen && newVehicleData && onNewVehicleDataChange && onRegisterVehicle && onCloseVehicleRegisterModal && (
|
||||
<ResizableDialog open={isVehicleRegisterModalOpen} onOpenChange={onCloseVehicleRegisterModal}>
|
||||
<ResizableDialogContent className="sm:max-w-[400px]">
|
||||
<ResizableDialogHeader>
|
||||
<ResizableDialogTitle>새 차량 등록</ResizableDialogTitle>
|
||||
<ResizableDialogDescription>
|
||||
<Dialog open={isVehicleRegisterModalOpen} onOpenChange={onCloseVehicleRegisterModal}>
|
||||
<DialogContent className="sm:max-w-[400px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>새 차량 등록</DialogTitle>
|
||||
<DialogDescription>
|
||||
새로운 차량 정보를 입력해주세요.
|
||||
</ResizableDialogDescription>
|
||||
</ResizableDialogHeader>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="space-y-2">
|
||||
|
|
@ -501,16 +501,16 @@ export function ProfileModal({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<ResizableDialogFooter>
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="outline" onClick={onCloseVehicleRegisterModal}>
|
||||
취소
|
||||
</Button>
|
||||
<Button type="button" onClick={onRegisterVehicle}>
|
||||
등록
|
||||
</Button>
|
||||
</ResizableDialogFooter>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import {
|
|||
DialogContent,
|
||||
DialogHeader,
|
||||
|
||||
} from "@/components/ui/resizable-dialog";
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
|
|
@ -186,13 +186,13 @@ export default function MailDetailModal({
|
|||
};
|
||||
|
||||
return (
|
||||
<ResizableDialog open={isOpen} onOpenChange={onClose}>
|
||||
<ResizableDialogContent className="max-w-4xl max-h-[90vh] overflow-hidden flex flex-col">
|
||||
<ResizableDialogHeader>
|
||||
<ResizableDialogTitle className="text-xl font-bold truncate">
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="max-w-4xl max-h-[90vh] overflow-hidden flex flex-col">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-xl font-bold truncate">
|
||||
메일 상세
|
||||
</ResizableDialogTitle>
|
||||
</ResizableDialogHeader>
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
{loading ? (
|
||||
<div className="flex justify-center items-center py-16">
|
||||
|
|
@ -375,8 +375,8 @@ export default function MailDetailModal({
|
|||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { ResizableDialog, ResizableDialogContent, ResizableDialogHeader, DialogTitle } from "@/components/ui/resizable-dialog";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
|
@ -141,9 +141,9 @@ export function LangKeyModal({
|
|||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-h-[90vh] max-w-4xl overflow-y-auto">
|
||||
<DialogContent className="max-h-[90vh] max-w-4xl overflow-hidden">
|
||||
<DialogHeader>
|
||||
<ResizableDialogTitle>{langKey ? "다국어 키 수정" : "새 다국어 키 추가"}</ResizableDialogTitle>
|
||||
<DialogTitle>{langKey ? "다국어 키 수정" : "새 다국어 키 추가"}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-6">
|
||||
|
|
|
|||
|
|
@ -210,7 +210,7 @@ export function OrderRegistrationModal({
|
|||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-[95vw] sm:max-w-[1200px] max-h-[90vh] overflow-y-auto">
|
||||
<DialogContent className="max-w-[95vw] sm:max-w-[1200px] max-h-[90vh] overflow-hidden">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-base sm:text-lg">수주 등록</DialogTitle>
|
||||
<DialogDescription className="text-xs sm:text-sm">
|
||||
|
|
|
|||
|
|
@ -4,11 +4,10 @@ import { useState, useEffect } from "react";
|
|||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
|
||||
|
||||
DialogHeader,
|
||||
|
||||
} from "@/components/ui/resizable-dialog";
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
|
@ -120,8 +119,8 @@ export function ReportCreateModal({ isOpen, onClose, onSuccess }: ReportCreateMo
|
|||
<Dialog open={isOpen} onOpenChange={handleClose}>
|
||||
<DialogContent className="sm:max-w-[500px]">
|
||||
<DialogHeader>
|
||||
<ResizableDialogTitle>새 리포트 생성</ResizableDialogTitle>
|
||||
<DialogDescription>새로운 리포트를 생성합니다. 필수 항목을 입력해주세요.</ResizableDialogDescription>
|
||||
<DialogTitle>새 리포트 생성</DialogTitle>
|
||||
<DialogDescription>새로운 리포트를 생성합니다. 필수 항목을 입력해주세요.</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4 py-4">
|
||||
|
|
@ -207,7 +206,7 @@ export function ReportCreateModal({ isOpen, onClose, onSuccess }: ReportCreateMo
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<ResizableDialogFooter>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={handleClose} disabled={isLoading}>
|
||||
취소
|
||||
</Button>
|
||||
|
|
@ -221,7 +220,7 @@ export function ReportCreateModal({ isOpen, onClose, onSuccess }: ReportCreateMo
|
|||
"생성"
|
||||
)}
|
||||
</Button>
|
||||
</ResizableDialogFooter>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -424,7 +424,7 @@ export default function CopyScreenModal({
|
|||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={handleClose}>
|
||||
<DialogContent className="max-w-[95vw] sm:max-w-[700px] max-h-[90vh] overflow-y-auto">
|
||||
<DialogContent className="max-w-[95vw] sm:max-w-[700px] max-h-[90vh] overflow-hidden">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Copy className="h-5 w-5" />
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
import { useEffect, useMemo, useState, useRef } from "react";
|
||||
import {
|
||||
ResizableDialog,
|
||||
ResizableDialogContent,
|
||||
ResizableDialogHeader,
|
||||
ResizableDialogTitle,
|
||||
ResizableDialogFooter,
|
||||
} from "@/components/ui/resizable-dialog";
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
|
@ -271,21 +271,11 @@ export default function CreateScreenModal({ open, onOpenChange, onCreated }: Cre
|
|||
};
|
||||
|
||||
return (
|
||||
<ResizableDialog open={open} onOpenChange={onOpenChange}>
|
||||
<ResizableDialogContent
|
||||
className="sm:max-w-lg"
|
||||
defaultWidth={600}
|
||||
defaultHeight={700}
|
||||
minWidth={500}
|
||||
minHeight={600}
|
||||
maxWidth={900}
|
||||
maxHeight={900}
|
||||
modalId="create-screen"
|
||||
userId={user?.userId}
|
||||
>
|
||||
<ResizableDialogHeader>
|
||||
<ResizableDialogTitle>새 화면 생성</ResizableDialogTitle>
|
||||
</ResizableDialogHeader>
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle>새 화면 생성</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
|
|
@ -603,15 +593,15 @@ export default function CreateScreenModal({ open, onOpenChange, onCreated }: Cre
|
|||
)}
|
||||
</div>
|
||||
|
||||
<ResizableDialogFooter className="mt-4">
|
||||
<DialogFooter className="mt-4">
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)} disabled={submitting}>
|
||||
취소
|
||||
</Button>
|
||||
<Button onClick={handleSubmit} disabled={!isValid || submitting} variant="default">
|
||||
생성
|
||||
</Button>
|
||||
</ResizableDialogFooter>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
ResizableDialog,
|
||||
ResizableDialogContent,
|
||||
ResizableDialogHeader,
|
||||
ResizableDialogTitle,
|
||||
ResizableDialogDescription,
|
||||
ResizableDialogFooter,
|
||||
} from "@/components/ui/resizable-dialog";
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import { InteractiveScreenViewerDynamic } from "@/components/screen/InteractiveScreenViewerDynamic";
|
||||
import { screenApi } from "@/lib/api/screen";
|
||||
import { ComponentData } from "@/types/screen";
|
||||
|
|
@ -661,14 +661,17 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
|
|||
}
|
||||
|
||||
// 화면관리에서 설정한 크기 = 컨텐츠 영역 크기
|
||||
// 실제 모달 크기 = 컨텐츠 + 헤더
|
||||
const headerHeight = 60; // DialogHeader
|
||||
const totalHeight = screenDimensions.height + headerHeight;
|
||||
// 실제 모달 크기 = 컨텐츠 + 헤더 + gap + padding
|
||||
const headerHeight = 52; // DialogHeader (타이틀 + border-b + py-3)
|
||||
const dialogGap = 16; // DialogContent gap-4
|
||||
const extraPadding = 24; // 추가 여백 (안전 마진)
|
||||
|
||||
const totalHeight = screenDimensions.height + headerHeight + dialogGap + extraPadding;
|
||||
|
||||
return {
|
||||
className: "overflow-hidden p-0",
|
||||
style: {
|
||||
width: `${Math.min(screenDimensions.width, window.innerWidth * 0.98)}px`,
|
||||
width: `${Math.min(screenDimensions.width + 48, window.innerWidth * 0.98)}px`, // 좌우 패딩 추가
|
||||
height: `${Math.min(totalHeight, window.innerHeight * 0.95)}px`,
|
||||
maxWidth: "98vw",
|
||||
maxHeight: "95vh",
|
||||
|
|
@ -679,32 +682,24 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
|
|||
const modalStyle = getModalStyle();
|
||||
|
||||
return (
|
||||
<ResizableDialog open={modalState.isOpen} onOpenChange={handleClose}>
|
||||
<ResizableDialogContent
|
||||
className={`${modalStyle.className} ${className || ""}`}
|
||||
<Dialog open={modalState.isOpen} onOpenChange={handleClose}>
|
||||
<DialogContent
|
||||
className={`${modalStyle.className} ${className || ""} max-w-none`}
|
||||
style={modalStyle.style}
|
||||
defaultWidth={800}
|
||||
defaultHeight={600}
|
||||
minWidth={600}
|
||||
minHeight={400}
|
||||
maxWidth={1400}
|
||||
maxHeight={1000}
|
||||
modalId={modalState.screenId ? `edit-modal-${modalState.screenId}` : undefined}
|
||||
userId={user?.userId}
|
||||
>
|
||||
<ResizableDialogHeader className="shrink-0 border-b px-4 py-3">
|
||||
<DialogHeader className="shrink-0 border-b px-4 py-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<ResizableDialogTitle className="text-base">{modalState.title || "데이터 수정"}</ResizableDialogTitle>
|
||||
<DialogTitle className="text-base">{modalState.title || "데이터 수정"}</DialogTitle>
|
||||
{modalState.description && !loading && (
|
||||
<ResizableDialogDescription className="text-muted-foreground text-xs">{modalState.description}</ResizableDialogDescription>
|
||||
<DialogDescription className="text-muted-foreground text-xs">{modalState.description}</DialogDescription>
|
||||
)}
|
||||
{loading && (
|
||||
<ResizableDialogDescription className="text-xs">{loading ? "화면을 불러오는 중입니다..." : ""}</ResizableDialogDescription>
|
||||
<DialogDescription className="text-xs">{loading ? "화면을 불러오는 중입니다..." : ""}</DialogDescription>
|
||||
)}
|
||||
</div>
|
||||
</ResizableDialogHeader>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="flex flex-1 items-center justify-center overflow-auto">
|
||||
<div className="flex flex-1 items-center justify-center overflow-y-auto [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-thumb]:bg-gray-300 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-track]:bg-transparent">
|
||||
{loading ? (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<div className="text-center">
|
||||
|
|
@ -795,8 +790,8 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import React, { useState, useCallback, useEffect } from "react";
|
||||
import { ResizableDialog, ResizableDialogContent, ResizableDialogHeader, DialogTitle } from "@/components/ui/resizable-dialog";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
|
@ -352,9 +352,9 @@ export const FileAttachmentDetailModal: React.FC<FileAttachmentDetailModalProps>
|
|||
<DialogContent className="max-w-4xl max-h-[90vh] overflow-hidden">
|
||||
<DialogHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<ResizableDialogTitle className="text-xl font-semibold">
|
||||
<DialogTitle className="text-xl font-semibold">
|
||||
파일 첨부 관리 - {component.label || component.id}
|
||||
</ResizableDialogTitle>
|
||||
</DialogTitle>
|
||||
<Button variant="ghost" size="sm" onClick={onClose}>
|
||||
<X className="w-4 h-4" />
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -2471,7 +2471,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
|
|||
|
||||
{/* 기존 데이터 추가 모달 (제거 예정 - SaveModal로 대체됨) */}
|
||||
<Dialog open={false} onOpenChange={() => {}}>
|
||||
<DialogContent className={`max-h-[80vh] overflow-y-auto ${getModalSizeClass()}`}>
|
||||
<DialogContent className={`max-h-[80vh] overflow-hidden ${getModalSizeClass()}`}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{component.addModalConfig?.title || "새 데이터 추가"}</DialogTitle>
|
||||
<DialogDescription>
|
||||
|
|
@ -2517,7 +2517,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
|
|||
|
||||
{/* 기존 데이터 수정 모달 (제거 예정 - SaveModal로 대체됨) */}
|
||||
<Dialog open={false} onOpenChange={() => {}}>
|
||||
<DialogContent className={`max-h-[80vh] overflow-y-auto ${getModalSizeClass()}`}>
|
||||
<DialogContent className={`max-h-[80vh] overflow-hidden ${getModalSizeClass()}`}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>데이터 수정</DialogTitle>
|
||||
<DialogDescription>선택된 데이터를 수정합니다.</DialogDescription>
|
||||
|
|
@ -2773,7 +2773,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
|
|||
|
||||
{/* 파일 관리 모달 */}
|
||||
<Dialog open={showFileManagementModal} onOpenChange={setShowFileManagementModal}>
|
||||
<DialogContent className="max-h-[80vh] max-w-4xl overflow-y-auto">
|
||||
<DialogContent className="max-h-[80vh] max-w-4xl overflow-hidden">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Folder className="h-5 w-5" />
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { Checkbox } from "@/components/ui/checkbox";
|
|||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Calendar } from "@/components/ui/calendar";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, ResizableDialog, ResizableDialogContent, ResizableDialogHeader } from "@/components/ui/dialog";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { CalendarIcon, File, Upload, X } from "lucide-react";
|
||||
import { format } from "date-fns";
|
||||
import { ko } from "date-fns/locale";
|
||||
|
|
|
|||
|
|
@ -3,8 +3,7 @@
|
|||
import React, { useState, useCallback, useEffect } from "react";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ResizableDialog, ResizableDialogContent, ResizableDialogHeader } from "@/components/ui/resizable-dialog";
|
||||
import { DialogTitle, DialogHeader } from "@/components/ui/dialog";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
import { uploadFilesAndCreateData } from "@/lib/api/file";
|
||||
import { toast } from "sonner";
|
||||
|
|
@ -776,17 +775,15 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
|||
|
||||
{/* 팝업 화면 렌더링 */}
|
||||
{popupScreen && (
|
||||
<ResizableDialog open={!!popupScreen} onOpenChange={() => setPopupScreen(null)}>
|
||||
<ResizableDialogContent
|
||||
className="overflow-hidden p-0"
|
||||
defaultWidth={popupScreen.size === "small" ? 600 : popupScreen.size === "large" ? 1400 : 1000}
|
||||
defaultHeight={800}
|
||||
minWidth={500}
|
||||
minHeight={400}
|
||||
maxWidth={1600}
|
||||
maxHeight={1200}
|
||||
modalId={`popup-screen-${popupScreen.screenId}`}
|
||||
userId={user?.userId || "guest"}
|
||||
<Dialog open={!!popupScreen} onOpenChange={() => setPopupScreen(null)}>
|
||||
<DialogContent
|
||||
className="overflow-hidden p-0 max-w-none"
|
||||
style={{
|
||||
width: popupScreen.size === "small" ? "600px" : popupScreen.size === "large" ? "1400px" : "1000px",
|
||||
height: "800px",
|
||||
maxWidth: "95vw",
|
||||
maxHeight: "90vh",
|
||||
}}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{popupScreen.title}</DialogTitle>
|
||||
|
|
@ -820,8 +817,8 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
|||
))}
|
||||
</div>
|
||||
)}
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import {
|
||||
ResizableDialog,
|
||||
ResizableDialogContent,
|
||||
ResizableDialogHeader,
|
||||
ResizableDialogTitle,
|
||||
ResizableDialogDescription,
|
||||
ResizableDialogFooter,
|
||||
} from "@/components/ui/resizable-dialog";
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
|
|
@ -345,26 +345,26 @@ export const MenuAssignmentModal: React.FC<MenuAssignmentModalProps> = ({
|
|||
|
||||
return (
|
||||
<>
|
||||
<ResizableDialog open={isOpen} onOpenChange={onClose}>
|
||||
<ResizableDialogContent className="max-w-2xl">
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="max-w-2xl">
|
||||
{assignmentSuccess ? (
|
||||
// 성공 화면
|
||||
<>
|
||||
<ResizableDialogHeader>
|
||||
<ResizableDialogTitle className="flex items-center gap-2">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-green-100">
|
||||
<svg className="h-5 w-5 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</div>
|
||||
{assignmentMessage.includes("나중에") ? "화면 저장 완료" : "화면 할당 완료"}
|
||||
</ResizableDialogTitle>
|
||||
<ResizableDialogDescription>
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
{assignmentMessage.includes("나중에")
|
||||
? "화면이 성공적으로 저장되었습니다. 나중에 메뉴에 할당할 수 있습니다."
|
||||
: "화면이 성공적으로 메뉴에 할당되었습니다."}
|
||||
</ResizableDialogDescription>
|
||||
</ResizableDialogHeader>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="rounded-lg border bg-green-50 p-4">
|
||||
|
|
@ -386,7 +386,7 @@ export const MenuAssignmentModal: React.FC<MenuAssignmentModalProps> = ({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<ResizableDialogFooter>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
onClick={() => {
|
||||
// 타이머 정리
|
||||
|
|
@ -407,19 +407,19 @@ export const MenuAssignmentModal: React.FC<MenuAssignmentModalProps> = ({
|
|||
<Monitor className="mr-2 h-4 w-4" />
|
||||
화면 목록으로 이동
|
||||
</Button>
|
||||
</ResizableDialogFooter>
|
||||
</DialogFooter>
|
||||
</>
|
||||
) : (
|
||||
// 기본 할당 화면
|
||||
<>
|
||||
<ResizableDialogHeader>
|
||||
<ResizableDialogTitle className="flex items-center gap-2">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Settings className="h-5 w-5" />
|
||||
메뉴에 화면 할당
|
||||
</ResizableDialogTitle>
|
||||
<ResizableDialogDescription>
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
저장된 화면을 메뉴에 할당하여 사용자가 접근할 수 있도록 설정합니다.
|
||||
</ResizableDialogDescription>
|
||||
</DialogDescription>
|
||||
{screenInfo && (
|
||||
<div className="bg-accent mt-2 rounded-lg border p-3">
|
||||
<div className="flex items-center gap-2">
|
||||
|
|
@ -432,7 +432,7 @@ export const MenuAssignmentModal: React.FC<MenuAssignmentModalProps> = ({
|
|||
{screenInfo.description && <p className="mt-1 text-sm text-blue-700">{screenInfo.description}</p>}
|
||||
</div>
|
||||
)}
|
||||
</ResizableDialogHeader>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* 메뉴 선택 (검색 기능 포함) */}
|
||||
|
|
@ -550,7 +550,7 @@ export const MenuAssignmentModal: React.FC<MenuAssignmentModalProps> = ({
|
|||
)}
|
||||
</div>
|
||||
|
||||
<ResizableDialogFooter className="flex gap-2">
|
||||
<DialogFooter className="flex gap-2">
|
||||
<Button variant="outline" onClick={handleAssignLater} disabled={assigning}>
|
||||
<X className="mr-2 h-4 w-4" />
|
||||
나중에 할당
|
||||
|
|
@ -572,22 +572,22 @@ export const MenuAssignmentModal: React.FC<MenuAssignmentModalProps> = ({
|
|||
</>
|
||||
)}
|
||||
</Button>
|
||||
</ResizableDialogFooter>
|
||||
</DialogFooter>
|
||||
</>
|
||||
)}
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* 화면 교체 확인 대화상자 */}
|
||||
<ResizableDialog open={showReplaceDialog} onOpenChange={setShowReplaceDialog}>
|
||||
<ResizableDialogContent className="max-w-md">
|
||||
<ResizableDialogHeader>
|
||||
<ResizableDialogTitle className="flex items-center gap-2">
|
||||
<Dialog open={showReplaceDialog} onOpenChange={setShowReplaceDialog}>
|
||||
<DialogContent className="max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Monitor className="h-5 w-5 text-orange-600" />
|
||||
화면 교체 확인
|
||||
</ResizableDialogTitle>
|
||||
<ResizableDialogDescription>선택한 메뉴에 이미 할당된 화면이 있습니다.</ResizableDialogDescription>
|
||||
</ResizableDialogHeader>
|
||||
</DialogTitle>
|
||||
<DialogDescription>선택한 메뉴에 이미 할당된 화면이 있습니다.</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* 기존 화면 목록 */}
|
||||
|
|
@ -628,7 +628,7 @@ export const MenuAssignmentModal: React.FC<MenuAssignmentModalProps> = ({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<ResizableDialogFooter className="flex gap-2">
|
||||
<DialogFooter className="flex gap-2">
|
||||
<Button variant="outline" onClick={() => setShowReplaceDialog(false)} disabled={assigning}>
|
||||
취소
|
||||
</Button>
|
||||
|
|
@ -652,9 +652,9 @@ export const MenuAssignmentModal: React.FC<MenuAssignmentModalProps> = ({
|
|||
</>
|
||||
)}
|
||||
</Button>
|
||||
</ResizableDialogFooter>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import React, { useState, createContext, useContext } from "react";
|
||||
import { ResizableDialog, ResizableDialogContent, ResizableDialogHeader, DialogTitle } from "@/components/ui/resizable-dialog";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Monitor, Tablet, Smartphone } from "lucide-react";
|
||||
import { ComponentData } from "@/types/screen";
|
||||
|
|
@ -76,7 +76,7 @@ export const ResponsivePreviewModal: React.FC<ResponsivePreviewModalProps> = ({
|
|||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="max-h-[95vh] max-w-[95vw] p-0">
|
||||
<DialogHeader className="border-b px-6 pt-6 pb-4">
|
||||
<ResizableDialogTitle>반응형 미리보기</ResizableDialogTitle>
|
||||
<DialogTitle>반응형 미리보기</DialogTitle>
|
||||
|
||||
{/* 디바이스 선택 버튼들 */}
|
||||
<div className="mt-4 flex gap-2">
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { ResizableDialog, ResizableDialogContent, ResizableDialogHeader, ResizableDialogTitle } from "@/components/ui/resizable-dialog";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { X, Save, Loader2 } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
|
|
@ -232,22 +232,19 @@ export const SaveModal: React.FC<SaveModalProps> = ({
|
|||
const dynamicSize = calculateDynamicSize();
|
||||
|
||||
return (
|
||||
<ResizableDialog open={isOpen} onOpenChange={(open) => !isSaving && !open && onClose()}>
|
||||
<ResizableDialogContent
|
||||
modalId={`save-modal-${screenId}`}
|
||||
<Dialog open={isOpen} onOpenChange={(open) => !isSaving && !open && onClose()}>
|
||||
<DialogContent
|
||||
style={{
|
||||
width: `${dynamicSize.width}px`,
|
||||
height: `${dynamicSize.height}px`, // 화면관리 설정 크기 그대로 사용
|
||||
minWidth: "400px",
|
||||
minHeight: "300px",
|
||||
}}
|
||||
defaultWidth={600} // 폴백용 기본값
|
||||
defaultHeight={400} // 폴백용 기본값
|
||||
minWidth={400}
|
||||
minHeight={300}
|
||||
className="gap-0 p-0"
|
||||
className="gap-0 p-0 max-w-none"
|
||||
>
|
||||
<ResizableDialogHeader className="border-b px-6 py-4 flex-shrink-0">
|
||||
<DialogHeader className="border-b px-6 py-4 flex-shrink-0">
|
||||
<div className="flex items-center justify-between">
|
||||
<ResizableDialogTitle className="text-lg font-semibold">{initialData ? "데이터 수정" : "데이터 등록"}</ResizableDialogTitle>
|
||||
<DialogTitle className="text-lg font-semibold">{initialData ? "데이터 수정" : "데이터 등록"}</DialogTitle>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button onClick={handleSave} disabled={isSaving} size="sm" className="gap-2">
|
||||
{isSaving ? (
|
||||
|
|
@ -267,7 +264,7 @@ export const SaveModal: React.FC<SaveModalProps> = ({
|
|||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</ResizableDialogHeader>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="overflow-auto p-6 flex-1">
|
||||
{loading ? (
|
||||
|
|
@ -376,7 +373,7 @@ export const SaveModal: React.FC<SaveModalProps> = ({
|
|||
<div className="text-muted-foreground py-12 text-center">화면에 컴포넌트가 없습니다.</div>
|
||||
)}
|
||||
</div>
|
||||
</ResizableDialogContent>
|
||||
</ResizableDialog>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { Input } from "@/components/ui/input";
|
|||
import { Badge } from "@/components/ui/badge";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { ResizableDialog, ResizableDialogContent, ResizableDialogDescription, DialogFooter, ResizableDialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { toast } from "sonner";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ const DialogContent = React.forwardRef<
|
|||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-[1000] grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg duration-200 sm:rounded-lg",
|
||||
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-[1000] flex w-full max-w-lg translate-x-[-50%] translate-y-[-50%] flex-col gap-4 border p-6 shadow-lg duration-200 sm:rounded-lg",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -54,12 +54,12 @@ const DialogContent = React.forwardRef<
|
|||
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
||||
|
||||
const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div className={cn("flex flex-col space-y-1.5 text-center sm:text-left", className)} {...props} />
|
||||
<div className={cn("flex flex-col space-y-1.5 text-center sm:text-left shrink-0", className)} {...props} />
|
||||
);
|
||||
DialogHeader.displayName = "DialogHeader";
|
||||
|
||||
const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)} {...props} />
|
||||
<div className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 shrink-0", className)} {...props} />
|
||||
);
|
||||
DialogFooter.displayName = "DialogFooter";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,601 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||
import { X } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
// 🆕 Context를 사용하여 open 상태 공유
|
||||
const ResizableDialogContext = React.createContext<{ open: boolean }>({ open: false });
|
||||
|
||||
// 🆕 ResizableDialog를 래핑하여 Context 제공
|
||||
const ResizableDialog: React.FC<React.ComponentProps<typeof DialogPrimitive.Root>> = ({
|
||||
children,
|
||||
open = false,
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<ResizableDialogContext.Provider value={{ open }}>
|
||||
<DialogPrimitive.Root open={open} {...props}>
|
||||
{children}
|
||||
</DialogPrimitive.Root>
|
||||
</ResizableDialogContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const ResizableDialogTrigger = DialogPrimitive.Trigger;
|
||||
|
||||
const ResizableDialogPortal = DialogPrimitive.Portal;
|
||||
|
||||
const ResizableDialogClose = DialogPrimitive.Close;
|
||||
|
||||
const ResizableDialogOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
ResizableDialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
||||
|
||||
interface ResizableDialogContentProps
|
||||
extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> {
|
||||
minWidth?: number;
|
||||
minHeight?: number;
|
||||
maxWidth?: number;
|
||||
maxHeight?: number;
|
||||
defaultWidth?: number;
|
||||
defaultHeight?: number;
|
||||
modalId?: string; // localStorage 저장용 고유 ID
|
||||
userId?: string; // 사용자별 저장용
|
||||
open?: boolean; // 🆕 모달 열림/닫힘 상태 (외부에서 전달)
|
||||
disableFlexLayout?: boolean; // 🆕 flex 레이아웃 비활성화 (absolute 레이아웃용)
|
||||
}
|
||||
|
||||
const ResizableDialogContent = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||
ResizableDialogContentProps
|
||||
>(
|
||||
(
|
||||
{
|
||||
className,
|
||||
children,
|
||||
minWidth = 400,
|
||||
minHeight = 300,
|
||||
maxWidth = 1600,
|
||||
maxHeight = 1200,
|
||||
defaultWidth = 600,
|
||||
defaultHeight = 500,
|
||||
modalId,
|
||||
userId = "guest",
|
||||
open: externalOpen, // 🆕 외부에서 전달받은 open 상태
|
||||
disableFlexLayout = false, // 🆕 flex 레이아웃 비활성화
|
||||
style: userStyle,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const contentRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
// 고정된 ID 생성 (한번 생성되면 컴포넌트 생명주기 동안 유지)
|
||||
const stableIdRef = React.useRef<string | null>(null);
|
||||
|
||||
if (!stableIdRef.current) {
|
||||
if (modalId) {
|
||||
stableIdRef.current = modalId;
|
||||
// // console.log("✅ ResizableDialog - 명시적 modalId 사용:", modalId);
|
||||
} else {
|
||||
// className 기반 ID 생성
|
||||
if (className) {
|
||||
const hash = className.split('').reduce((acc, char) => {
|
||||
return ((acc << 5) - acc) + char.charCodeAt(0);
|
||||
}, 0);
|
||||
stableIdRef.current = `modal-${Math.abs(hash).toString(36)}`;
|
||||
// console.log("🔄 ResizableDialog - className 기반 ID 생성:", { className, generatedId: stableIdRef.current });
|
||||
} else if (userStyle) {
|
||||
// userStyle 기반 ID 생성
|
||||
const styleStr = JSON.stringify(userStyle);
|
||||
const hash = styleStr.split('').reduce((acc, char) => {
|
||||
return ((acc << 5) - acc) + char.charCodeAt(0);
|
||||
}, 0);
|
||||
stableIdRef.current = `modal-${Math.abs(hash).toString(36)}`;
|
||||
// console.log("🔄 ResizableDialog - userStyle 기반 ID 생성:", { userStyle, generatedId: stableIdRef.current });
|
||||
} else {
|
||||
// 기본 ID
|
||||
stableIdRef.current = 'modal-default';
|
||||
// console.log("⚠️ ResizableDialog - 기본 ID 사용 (모든 모달이 같은 크기 공유)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const effectiveModalId = stableIdRef.current;
|
||||
|
||||
// 실제 렌더링된 크기를 감지하여 초기 크기로 사용
|
||||
const getInitialSize = React.useCallback(() => {
|
||||
if (typeof window === 'undefined') return { width: defaultWidth, height: defaultHeight };
|
||||
|
||||
// 1순위: userStyle에서 크기 추출 (화면관리에서 지정한 크기 - 항상 초기값으로 사용)
|
||||
if (userStyle) {
|
||||
const styleWidth = typeof userStyle.width === 'string'
|
||||
? parseInt(userStyle.width)
|
||||
: userStyle.width;
|
||||
const styleHeight = typeof userStyle.height === 'string'
|
||||
? parseInt(userStyle.height)
|
||||
: userStyle.height;
|
||||
|
||||
if (styleWidth && styleHeight) {
|
||||
const finalSize = {
|
||||
width: Math.max(minWidth, Math.min(maxWidth, styleWidth)),
|
||||
height: Math.max(minHeight, Math.min(maxHeight, styleHeight)),
|
||||
};
|
||||
return finalSize;
|
||||
}
|
||||
}
|
||||
|
||||
// 2순위: 현재 렌더링된 크기 사용 (주석처리 - 모달이 열린 후 늘어나는 현상 방지)
|
||||
// if (contentRef.current) {
|
||||
// const rect = contentRef.current.getBoundingClientRect();
|
||||
// if (rect.width > 0 && rect.height > 0) {
|
||||
// return {
|
||||
// width: Math.max(minWidth, Math.min(maxWidth, rect.width)),
|
||||
// height: Math.max(minHeight, Math.min(maxHeight, rect.height)),
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
|
||||
// 3순위: defaultWidth/defaultHeight 사용
|
||||
return { width: defaultWidth, height: defaultHeight };
|
||||
}, [defaultWidth, defaultHeight, minWidth, minHeight, maxWidth, maxHeight, userStyle]);
|
||||
|
||||
const [size, setSize] = React.useState(getInitialSize);
|
||||
const [isResizing, setIsResizing] = React.useState(false);
|
||||
const [resizeDirection, setResizeDirection] = React.useState<string>("");
|
||||
const [isInitialized, setIsInitialized] = React.useState(false);
|
||||
|
||||
// userStyle이 변경되면 크기 업데이트 (화면 데이터 로딩 완료 시)
|
||||
React.useEffect(() => {
|
||||
// 1. localStorage에서 사용자가 리사이징한 크기 확인
|
||||
let savedSize: { width: number; height: number; userResized: boolean } | null = null;
|
||||
|
||||
if (effectiveModalId && typeof window !== 'undefined') {
|
||||
try {
|
||||
const storageKey = `modal_size_${effectiveModalId}_${userId}`;
|
||||
const saved = localStorage.getItem(storageKey);
|
||||
|
||||
if (saved) {
|
||||
const parsed = JSON.parse(saved);
|
||||
if (parsed.userResized) {
|
||||
savedSize = {
|
||||
width: Math.max(minWidth, Math.min(maxWidth, parsed.width)),
|
||||
height: Math.max(minHeight, Math.min(maxHeight, parsed.height)),
|
||||
userResized: true,
|
||||
};
|
||||
// console.log("💾 사용자가 리사이징한 크기 복원:", savedSize);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("❌ 모달 크기 복원 실패:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 우선순위: 사용자 리사이징 > userStyle > 기본값
|
||||
if (savedSize && savedSize.userResized) {
|
||||
// 사용자가 리사이징한 크기 우선
|
||||
setSize({ width: savedSize.width, height: savedSize.height });
|
||||
setUserResized(true);
|
||||
} else if (userStyle && userStyle.width && userStyle.height) {
|
||||
// 화면관리에서 설정한 크기
|
||||
const styleWidth = typeof userStyle.width === 'string'
|
||||
? parseInt(userStyle.width)
|
||||
: userStyle.width;
|
||||
const styleHeight = typeof userStyle.height === 'string'
|
||||
? parseInt(userStyle.height)
|
||||
: userStyle.height;
|
||||
|
||||
if (styleWidth && styleHeight) {
|
||||
const newSize = {
|
||||
width: Math.max(minWidth, Math.min(maxWidth, styleWidth)),
|
||||
height: Math.max(minHeight, Math.min(maxHeight, styleHeight)),
|
||||
};
|
||||
setSize(newSize);
|
||||
}
|
||||
}
|
||||
}, [userStyle, minWidth, maxWidth, minHeight, maxHeight, effectiveModalId, userId]);
|
||||
const [lastModalId, setLastModalId] = React.useState<string | null>(null);
|
||||
const [userResized, setUserResized] = React.useState(false); // 사용자가 실제로 리사이징했는지 추적
|
||||
|
||||
// 🆕 Context에서 open 상태 가져오기 (우선순위: externalOpen > context.open)
|
||||
const context = React.useContext(ResizableDialogContext);
|
||||
const actualOpen = externalOpen !== undefined ? externalOpen : context.open;
|
||||
|
||||
// 🆕 모달이 닫혔다가 다시 열릴 때 초기화 리셋
|
||||
const [wasOpen, setWasOpen] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
// console.log("🔍 모달 상태 변화 감지:", { actualOpen, wasOpen, externalOpen, contextOpen: context.open, effectiveModalId });
|
||||
|
||||
if (actualOpen && !wasOpen) {
|
||||
// 모달이 방금 열림
|
||||
// console.log("🔓 모달 열림 감지, 초기화 리셋:", { effectiveModalId });
|
||||
setIsInitialized(false);
|
||||
setWasOpen(true);
|
||||
} else if (!actualOpen && wasOpen) {
|
||||
// 모달이 방금 닫힘
|
||||
// console.log("🔒 모달 닫힘 감지:", { effectiveModalId });
|
||||
setWasOpen(false);
|
||||
}
|
||||
}, [actualOpen, wasOpen, effectiveModalId, externalOpen, context.open]);
|
||||
|
||||
// modalId가 변경되면 초기화 리셋 (다른 모달이 열린 경우)
|
||||
React.useEffect(() => {
|
||||
if (effectiveModalId !== lastModalId) {
|
||||
// console.log("🔄 모달 ID 변경 감지, 초기화 리셋:", { 이전: lastModalId, 현재: effectiveModalId, isInitialized });
|
||||
setIsInitialized(false);
|
||||
setUserResized(false); // 사용자 리사이징 플래그도 리셋
|
||||
setLastModalId(effectiveModalId);
|
||||
}
|
||||
}, [effectiveModalId, lastModalId, isInitialized]);
|
||||
|
||||
// 모달이 열릴 때 초기 크기 설정 (localStorage와 내용 크기 중 큰 값 사용)
|
||||
// 주석처리 - 사용자가 설정한 크기(userStyle)만 사용하도록 변경
|
||||
// React.useEffect(() => {
|
||||
// // console.log("🔍 초기 크기 설정 useEffect 실행:", { isInitialized, hasContentRef: !!contentRef.current, effectiveModalId });
|
||||
//
|
||||
// if (!isInitialized) {
|
||||
// // 내용의 실제 크기 측정 (약간의 지연 후, contentRef가 준비될 때까지 대기)
|
||||
// // 여러 번 시도하여 contentRef가 준비될 때까지 대기
|
||||
// let attempts = 0;
|
||||
// const maxAttempts = 10;
|
||||
//
|
||||
// const measureContent = () => {
|
||||
// attempts++;
|
||||
//
|
||||
// // scrollHeight/scrollWidth를 사용하여 실제 내용 크기 측정 (스크롤 포함)
|
||||
// let contentWidth = defaultWidth;
|
||||
// let contentHeight = defaultHeight;
|
||||
//
|
||||
// // if (contentRef.current) {
|
||||
// // // scrollHeight/scrollWidth 그대로 사용 (여유 공간 제거)
|
||||
// // contentWidth = contentRef.current.scrollWidth || defaultWidth;
|
||||
// // contentHeight = contentRef.current.scrollHeight || defaultHeight;
|
||||
// //
|
||||
// // // console.log("📏 모달 내용 크기 측정:", { attempt: attempts, scrollWidth: contentRef.current.scrollWidth, scrollHeight: contentRef.current.scrollHeight, clientWidth: contentRef.current.clientWidth, clientHeight: contentRef.current.clientHeight, contentWidth, contentHeight });
|
||||
// // } else {
|
||||
// // // console.log("⚠️ contentRef 없음, 재시도:", { attempt: attempts, maxAttempts, defaultWidth, defaultHeight });
|
||||
// //
|
||||
// // // contentRef가 아직 없으면 재시도
|
||||
// // if (attempts < maxAttempts) {
|
||||
// // setTimeout(measureContent, 100);
|
||||
// // return;
|
||||
// // }
|
||||
// // }
|
||||
//
|
||||
// // 패딩 추가 (p-6 * 2 = 48px)
|
||||
// const paddingAndMargin = 48;
|
||||
// const initialSize = getInitialSize();
|
||||
//
|
||||
// // 내용 크기 기반 최소 크기 계산
|
||||
// const contentBasedSize = {
|
||||
// width: Math.max(minWidth, Math.min(maxWidth, Math.max(contentWidth + paddingAndMargin, initialSize.width))),
|
||||
// height: Math.max(minHeight, Math.min(maxHeight, Math.max(contentHeight + paddingAndMargin, initialSize.height))),
|
||||
// };
|
||||
//
|
||||
// // console.log("📐 내용 기반 크기:", contentBasedSize);
|
||||
//
|
||||
// // localStorage에서 저장된 크기 확인
|
||||
// let finalSize = contentBasedSize;
|
||||
//
|
||||
// if (effectiveModalId && typeof window !== 'undefined') {
|
||||
// try {
|
||||
// const storageKey = `modal_size_${effectiveModalId}_${userId}`;
|
||||
// const saved = localStorage.getItem(storageKey);
|
||||
//
|
||||
// // console.log("📦 localStorage 확인:", { effectiveModalId, userId, storageKey, saved: saved ? "있음" : "없음" });
|
||||
//
|
||||
// if (saved) {
|
||||
// const parsed = JSON.parse(saved);
|
||||
//
|
||||
// // userResized 플래그 확인
|
||||
// if (parsed.userResized) {
|
||||
// const savedSize = {
|
||||
// width: Math.max(minWidth, Math.min(maxWidth, parsed.width)),
|
||||
// height: Math.max(minHeight, Math.min(maxHeight, parsed.height)),
|
||||
// };
|
||||
//
|
||||
// // console.log("💾 사용자가 리사이징한 크기 복원:", savedSize);
|
||||
//
|
||||
// // ✅ 중요: 사용자가 명시적으로 리사이징한 경우, 사용자 크기를 우선 사용
|
||||
// // (사용자가 의도적으로 작게 만든 것을 존중)
|
||||
// finalSize = savedSize;
|
||||
// setUserResized(true);
|
||||
//
|
||||
// // console.log("✅ 최종 크기 (사용자가 설정한 크기 우선 적용):", { savedSize, contentBasedSize, finalSize, note: "사용자가 리사이징한 크기를 그대로 사용합니다" });
|
||||
// } else {
|
||||
// // console.log("ℹ️ 자동 계산된 크기는 무시, 내용 크기 사용");
|
||||
// }
|
||||
// } else {
|
||||
// // console.log("ℹ️ localStorage에 저장된 크기 없음, 내용 크기 사용");
|
||||
// }
|
||||
// } catch (error) {
|
||||
// // console.error("❌ 모달 크기 복원 실패:", error);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// setSize(finalSize);
|
||||
// setIsInitialized(true);
|
||||
// };
|
||||
//
|
||||
// // 첫 시도는 300ms 후에 시작
|
||||
// setTimeout(measureContent, 300);
|
||||
// }
|
||||
// }, [isInitialized, getInitialSize, effectiveModalId, userId, minWidth, maxWidth, minHeight, maxHeight, defaultWidth, defaultHeight]);
|
||||
|
||||
const startResize = (direction: string) => (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsResizing(true);
|
||||
setResizeDirection(direction);
|
||||
|
||||
const startX = e.clientX;
|
||||
const startY = e.clientY;
|
||||
const startWidth = size.width;
|
||||
const startHeight = size.height;
|
||||
|
||||
const handleMouseMove = (moveEvent: MouseEvent) => {
|
||||
const deltaX = moveEvent.clientX - startX;
|
||||
const deltaY = moveEvent.clientY - startY;
|
||||
|
||||
let newWidth = startWidth;
|
||||
let newHeight = startHeight;
|
||||
|
||||
if (direction.includes("e")) {
|
||||
newWidth = Math.max(minWidth, Math.min(maxWidth, startWidth + deltaX));
|
||||
}
|
||||
if (direction.includes("w")) {
|
||||
newWidth = Math.max(minWidth, Math.min(maxWidth, startWidth - deltaX));
|
||||
}
|
||||
if (direction.includes("s")) {
|
||||
newHeight = Math.max(minHeight, Math.min(maxHeight, startHeight + deltaY));
|
||||
}
|
||||
if (direction.includes("n")) {
|
||||
newHeight = Math.max(minHeight, Math.min(maxHeight, startHeight - deltaY));
|
||||
}
|
||||
|
||||
setSize({ width: newWidth, height: newHeight });
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
setIsResizing(false);
|
||||
setResizeDirection("");
|
||||
document.removeEventListener("mousemove", handleMouseMove);
|
||||
document.removeEventListener("mouseup", handleMouseUp);
|
||||
|
||||
// 사용자가 리사이징했음을 표시
|
||||
setUserResized(true);
|
||||
|
||||
// ✅ 중요: 현재 실제 DOM 크기를 저장 (state가 아닌 실제 크기)
|
||||
if (effectiveModalId && typeof window !== 'undefined' && contentRef.current) {
|
||||
try {
|
||||
const storageKey = `modal_size_${effectiveModalId}_${userId}`;
|
||||
|
||||
// contentRef의 부모 요소(모달 컨테이너)의 실제 크기 사용
|
||||
const modalElement = contentRef.current.parentElement;
|
||||
const actualWidth = modalElement?.offsetWidth || size.width;
|
||||
const actualHeight = modalElement?.offsetHeight || size.height;
|
||||
|
||||
const currentSize = {
|
||||
width: actualWidth,
|
||||
height: actualHeight,
|
||||
userResized: true, // 사용자가 직접 리사이징했음을 표시
|
||||
};
|
||||
localStorage.setItem(storageKey, JSON.stringify(currentSize));
|
||||
// console.log("💾 localStorage에 크기 저장 (사용자 리사이징):", { effectiveModalId, userId, storageKey, size: currentSize, stateSize: { width: size.width, height: size.height } });
|
||||
} catch (error) {
|
||||
// console.error("❌ 모달 크기 저장 실패:", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("mousemove", handleMouseMove);
|
||||
document.addEventListener("mouseup", handleMouseUp);
|
||||
};
|
||||
|
||||
return (
|
||||
<ResizableDialogPortal>
|
||||
<ResizableDialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
{...props}
|
||||
className={cn(
|
||||
"fixed left-[50%] top-[50%] z-50 translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||
isResizing && "select-none",
|
||||
className
|
||||
)}
|
||||
style={{
|
||||
...userStyle,
|
||||
width: `${size.width}px`,
|
||||
height: `${size.height}px`,
|
||||
maxWidth: "95vw",
|
||||
maxHeight: "95vh",
|
||||
minWidth: `${minWidth}px`,
|
||||
minHeight: `${minHeight}px`,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
ref={contentRef}
|
||||
className="h-full w-full relative"
|
||||
style={{ display: 'block', overflow: 'auto', pointerEvents: 'auto', zIndex: 1 }}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
{/* 리사이즈 핸들 */}
|
||||
{/* 오른쪽 */}
|
||||
<div
|
||||
className="absolute right-0 top-0 h-full w-2 cursor-ew-resize hover:bg-primary/20 transition-colors"
|
||||
style={{ pointerEvents: 'auto', zIndex: 10 }}
|
||||
onMouseDown={startResize("e")}
|
||||
/>
|
||||
{/* 아래 */}
|
||||
<div
|
||||
className="absolute bottom-0 left-0 w-full h-2 cursor-ns-resize hover:bg-primary/20 transition-colors"
|
||||
style={{ pointerEvents: 'auto', zIndex: 10 }}
|
||||
onMouseDown={startResize("s")}
|
||||
/>
|
||||
{/* 오른쪽 아래 */}
|
||||
<div
|
||||
className="absolute right-0 bottom-0 w-4 h-4 cursor-nwse-resize hover:bg-primary/30 transition-colors"
|
||||
style={{ pointerEvents: 'auto', zIndex: 10 }}
|
||||
onMouseDown={startResize("se")}
|
||||
/>
|
||||
{/* 왼쪽 */}
|
||||
<div
|
||||
className="absolute left-0 top-0 h-full w-2 cursor-ew-resize hover:bg-primary/20 transition-colors"
|
||||
style={{ pointerEvents: 'auto', zIndex: 10 }}
|
||||
onMouseDown={startResize("w")}
|
||||
/>
|
||||
{/* 위 */}
|
||||
<div
|
||||
className="absolute top-0 left-0 w-full h-2 cursor-ns-resize hover:bg-primary/20 transition-colors"
|
||||
style={{ pointerEvents: 'auto', zIndex: 10 }}
|
||||
onMouseDown={startResize("n")}
|
||||
/>
|
||||
{/* 왼쪽 아래 */}
|
||||
<div
|
||||
className="absolute left-0 bottom-0 w-4 h-4 cursor-nesw-resize hover:bg-primary/30 transition-colors"
|
||||
style={{ pointerEvents: 'auto', zIndex: 10 }}
|
||||
onMouseDown={startResize("sw")}
|
||||
/>
|
||||
{/* 오른쪽 위 */}
|
||||
<div
|
||||
className="absolute right-0 top-0 w-4 h-4 cursor-nesw-resize hover:bg-primary/30 transition-colors"
|
||||
style={{ pointerEvents: 'auto', zIndex: 10 }}
|
||||
onMouseDown={startResize("ne")}
|
||||
/>
|
||||
{/* 왼쪽 위 */}
|
||||
<div
|
||||
className="absolute left-0 top-0 w-4 h-4 cursor-nwse-resize hover:bg-primary/30 transition-colors"
|
||||
style={{ pointerEvents: 'auto', zIndex: 10 }}
|
||||
onMouseDown={startResize("nw")}
|
||||
/>
|
||||
|
||||
{/* 리셋 버튼 (사용자가 리사이징한 경우만 표시) */}
|
||||
{userResized && (
|
||||
<button
|
||||
onClick={() => {
|
||||
// localStorage에서 저장된 크기 삭제
|
||||
if (effectiveModalId && typeof window !== 'undefined') {
|
||||
const storageKey = `modal_size_${effectiveModalId}_${userId}`;
|
||||
localStorage.removeItem(storageKey);
|
||||
console.log("🗑️ 저장된 모달 크기 삭제:", storageKey);
|
||||
}
|
||||
|
||||
// 화면관리 설정 크기로 복원
|
||||
const initialSize = getInitialSize();
|
||||
setSize(initialSize);
|
||||
setUserResized(false);
|
||||
console.log("🔄 기본 크기로 리셋:", initialSize);
|
||||
}}
|
||||
className="absolute right-12 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
||||
style={{ zIndex: 20 }}
|
||||
title="기본 크기로 리셋"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/>
|
||||
<path d="M21 3v5h-5"/>
|
||||
<path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/>
|
||||
<path d="M3 21v-5h5"/>
|
||||
</svg>
|
||||
<span className="sr-only">기본 크기로 리셋</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
<DialogPrimitive.Close
|
||||
className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"
|
||||
style={{ zIndex: 20 }}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
</ResizableDialogPortal>
|
||||
);
|
||||
}
|
||||
);
|
||||
ResizableDialogContent.displayName = DialogPrimitive.Content.displayName;
|
||||
|
||||
const ResizableDialogHeader = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col space-y-1.5 text-center sm:text-left flex-shrink-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
ResizableDialogHeader.displayName = "ResizableDialogHeader";
|
||||
|
||||
const ResizableDialogFooter = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 flex-shrink-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
ResizableDialogFooter.displayName = "ResizableDialogFooter";
|
||||
|
||||
const ResizableDialogTitle = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"text-lg font-semibold leading-none tracking-tight",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
ResizableDialogTitle.displayName = DialogPrimitive.Title.displayName;
|
||||
|
||||
const ResizableDialogDescription = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
ResizableDialogDescription.displayName =
|
||||
DialogPrimitive.Description.displayName;
|
||||
|
||||
export {
|
||||
ResizableDialog,
|
||||
ResizableDialogPortal,
|
||||
ResizableDialogOverlay,
|
||||
ResizableDialogClose,
|
||||
ResizableDialogTrigger,
|
||||
ResizableDialogContent,
|
||||
ResizableDialogHeader,
|
||||
ResizableDialogFooter,
|
||||
ResizableDialogTitle,
|
||||
ResizableDialogDescription,
|
||||
};
|
||||
|
||||
|
|
@ -428,6 +428,31 @@ export const RepeaterInput: React.FC<RepeaterInputProps> = ({
|
|||
return <span className="text-sm">{option?.label || value}</span>;
|
||||
}
|
||||
|
||||
// 🆕 카테고리 매핑이 있는 경우 라벨로 변환 (조인된 테이블의 카테고리 필드)
|
||||
const mapping = categoryMappings[field.name];
|
||||
if (mapping && value) {
|
||||
const valueStr = String(value);
|
||||
const categoryData = mapping[valueStr];
|
||||
if (categoryData) {
|
||||
// 색상이 있으면 배지로 표시
|
||||
if (categoryData.color && categoryData.color !== "none" && categoryData.color !== "#64748b") {
|
||||
return (
|
||||
<Badge
|
||||
style={{
|
||||
backgroundColor: categoryData.color,
|
||||
borderColor: categoryData.color,
|
||||
}}
|
||||
className="text-white"
|
||||
>
|
||||
{categoryData.label}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
// 색상이 없으면 텍스트로 표시
|
||||
return <span className="text-sm text-foreground">{categoryData.label}</span>;
|
||||
}
|
||||
}
|
||||
|
||||
// 일반 텍스트
|
||||
return (
|
||||
<span className="text-sm text-foreground">
|
||||
|
|
@ -556,44 +581,40 @@ export const RepeaterInput: React.FC<RepeaterInputProps> = ({
|
|||
}
|
||||
};
|
||||
|
||||
// 카테고리 매핑 로드 (카테고리 필드가 있을 때 자동 로드)
|
||||
// 카테고리 매핑 로드 (카테고리 필드 + readonly 필드에 대해 자동 로드)
|
||||
// 테이블 리스트와 동일한 API 사용: /table-categories/{tableName}/{columnName}/values
|
||||
useEffect(() => {
|
||||
// 카테고리 타입 필드 + readonly 필드 (조인된 테이블에서 온 데이터일 가능성)
|
||||
const categoryFields = fields.filter(f => f.type === "category");
|
||||
if (categoryFields.length === 0) return;
|
||||
const readonlyFields = fields.filter(f => f.displayMode === "readonly" && f.type === "text");
|
||||
|
||||
if (categoryFields.length === 0 && readonlyFields.length === 0) return;
|
||||
|
||||
const loadCategoryMappings = async () => {
|
||||
const apiClient = (await import("@/lib/api/client")).apiClient;
|
||||
|
||||
// 1. 카테고리 타입 필드 매핑 로드
|
||||
for (const field of categoryFields) {
|
||||
const columnName = field.name; // 실제 컬럼명
|
||||
const categoryCode = field.categoryCode || columnName;
|
||||
const columnName = field.name;
|
||||
|
||||
// 이미 로드된 경우 스킵
|
||||
if (categoryMappings[columnName]) continue;
|
||||
|
||||
try {
|
||||
// config에서 targetTable 가져오기, 없으면 스킵
|
||||
const tableName = config.targetTable;
|
||||
if (!tableName) {
|
||||
console.warn(`[RepeaterInput] targetTable이 설정되지 않아 카테고리 매핑을 로드할 수 없습니다.`);
|
||||
continue;
|
||||
}
|
||||
if (!tableName) continue;
|
||||
|
||||
console.log(`📡 [RepeaterInput] 카테고리 매핑 로드: ${tableName}/${columnName}`);
|
||||
|
||||
// 테이블 리스트와 동일한 API 사용
|
||||
const response = await apiClient.get(`/table-categories/${tableName}/${columnName}/values`);
|
||||
|
||||
if (response.data.success && response.data.data && Array.isArray(response.data.data)) {
|
||||
const mapping: Record<string, { label: string; color: string }> = {};
|
||||
|
||||
response.data.data.forEach((item: any) => {
|
||||
// valueCode를 문자열로 변환하여 키로 사용 (테이블 리스트와 동일)
|
||||
const key = String(item.valueCode);
|
||||
mapping[key] = {
|
||||
label: item.valueLabel || key,
|
||||
color: item.color || "#64748b", // color 필드 사용 (DB 컬럼명과 동일)
|
||||
color: item.color || "#64748b",
|
||||
};
|
||||
});
|
||||
|
||||
|
|
@ -608,6 +629,50 @@ export const RepeaterInput: React.FC<RepeaterInputProps> = ({
|
|||
console.error(`❌ [RepeaterInput] 카테고리 매핑 로드 실패 (${columnName}):`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 🆕 readonly 필드에 대해 조인된 테이블 (item_info)에서 카테고리 매핑 로드
|
||||
// material, division 등 조인된 테이블의 카테고리 필드
|
||||
const joinedTableFields = ['material', 'division', 'status', 'currency_code'];
|
||||
const fieldsToLoadFromJoinedTable = readonlyFields.filter(f => joinedTableFields.includes(f.name));
|
||||
|
||||
if (fieldsToLoadFromJoinedTable.length > 0) {
|
||||
// item_info 테이블에서 카테고리 매핑 로드
|
||||
const joinedTableName = 'item_info';
|
||||
|
||||
for (const field of fieldsToLoadFromJoinedTable) {
|
||||
const columnName = field.name;
|
||||
|
||||
if (categoryMappings[columnName]) continue;
|
||||
|
||||
try {
|
||||
console.log(`📡 [RepeaterInput] 조인 테이블 카테고리 매핑 로드: ${joinedTableName}/${columnName}`);
|
||||
|
||||
const response = await apiClient.get(`/table-categories/${joinedTableName}/${columnName}/values`);
|
||||
|
||||
if (response.data.success && response.data.data && Array.isArray(response.data.data)) {
|
||||
const mapping: Record<string, { label: string; color: string }> = {};
|
||||
|
||||
response.data.data.forEach((item: any) => {
|
||||
const key = String(item.valueCode);
|
||||
mapping[key] = {
|
||||
label: item.valueLabel || key,
|
||||
color: item.color || "#64748b",
|
||||
};
|
||||
});
|
||||
|
||||
console.log(`✅ [RepeaterInput] 조인 테이블 카테고리 매핑 로드 완료 [${columnName}]:`, mapping);
|
||||
|
||||
setCategoryMappings(prev => ({
|
||||
...prev,
|
||||
[columnName]: mapping,
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
// 카테고리가 없는 필드는 무시
|
||||
console.log(`ℹ️ [RepeaterInput] 조인 테이블 카테고리 없음 (${columnName})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
loadCategoryMappings();
|
||||
|
|
|
|||
|
|
@ -968,7 +968,7 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
|
|||
|
||||
{/* 상세보기 모달 */}
|
||||
<Dialog open={viewModalOpen} onOpenChange={setViewModalOpen}>
|
||||
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
|
||||
<DialogContent className="max-w-2xl max-h-[80vh] overflow-hidden">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<span className="text-lg">📋</span>
|
||||
|
|
@ -1041,7 +1041,7 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
|
|||
|
||||
{/* 편집 모달 */}
|
||||
<Dialog open={editModalOpen} onOpenChange={setEditModalOpen}>
|
||||
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
|
||||
<DialogContent className="max-w-2xl max-h-[80vh] overflow-hidden">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<span className="text-lg">✏️</span>
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ export function EntitySearchModal({
|
|||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-[95vw] sm:max-w-[800px] max-h-[90vh] overflow-y-auto">
|
||||
<DialogContent className="max-w-[95vw] sm:max-w-[800px] max-h-[90vh] overflow-hidden">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-base sm:text-lg">{modalTitle}</DialogTitle>
|
||||
<DialogDescription className="text-xs sm:text-sm">
|
||||
|
|
|
|||
|
|
@ -491,7 +491,7 @@ export const FileViewerModal: React.FC<FileViewerModalProps> = ({ file, isOpen,
|
|||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={() => {}}>
|
||||
<DialogContent className="max-h-[90vh] max-w-4xl overflow-y-auto [&>button]:hidden">
|
||||
<DialogContent className="max-h-[90vh] max-w-4xl overflow-hidden [&>button]:hidden">
|
||||
<DialogHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-3">
|
||||
|
|
@ -506,7 +506,7 @@ export const FileViewerModal: React.FC<FileViewerModalProps> = ({ file, isOpen,
|
|||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="flex-1 overflow-y-auto">{renderPreview()}</div>
|
||||
<div className="flex-1 overflow-hidden">{renderPreview()}</div>
|
||||
|
||||
{/* 파일 정보 및 액션 버튼 */}
|
||||
<div className="mt-2 flex items-center space-x-4 text-sm text-gray-500">
|
||||
|
|
|
|||
|
|
@ -166,7 +166,7 @@ export function ItemSelectionModal({
|
|||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-[95vw] sm:max-w-[900px] max-h-[90vh] overflow-y-auto">
|
||||
<DialogContent className="max-w-[95vw] sm:max-w-[900px] flex flex-col">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-base sm:text-lg">{modalTitle}</DialogTitle>
|
||||
<DialogDescription className="text-xs sm:text-sm">
|
||||
|
|
@ -222,8 +222,8 @@ export function ItemSelectionModal({
|
|||
)}
|
||||
|
||||
{/* 검색 결과 테이블 */}
|
||||
<div className="border rounded-md overflow-hidden">
|
||||
<div className="overflow-x-auto">
|
||||
<div className="border rounded-md overflow-hidden flex-1 min-h-0">
|
||||
<div className="overflow-auto max-h-[50vh]">
|
||||
<table className="w-full text-xs sm:text-sm">
|
||||
<thead className="bg-muted">
|
||||
<tr>
|
||||
|
|
|
|||
|
|
@ -969,15 +969,30 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
|
||||
try {
|
||||
const mappings: Record<string, Record<string, { label: string; color?: string }>> = {};
|
||||
const apiClient = (await import("@/lib/api/client")).apiClient;
|
||||
|
||||
for (const columnName of categoryColumns) {
|
||||
try {
|
||||
// 🆕 엔티티 조인 컬럼 처리: "테이블명.컬럼명" 형태인지 확인
|
||||
let targetTable = tableConfig.selectedTable;
|
||||
let targetColumn = columnName;
|
||||
|
||||
if (columnName.includes(".")) {
|
||||
const parts = columnName.split(".");
|
||||
targetTable = parts[0]; // 조인된 테이블명 (예: item_info)
|
||||
targetColumn = parts[1]; // 실제 컬럼명 (예: material)
|
||||
console.log(`🔗 [TableList] 엔티티 조인 컬럼 감지:`, {
|
||||
originalColumn: columnName,
|
||||
targetTable,
|
||||
targetColumn,
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`📡 [TableList] API 호출 시작 [${columnName}]:`, {
|
||||
url: `/table-categories/${tableConfig.selectedTable}/${columnName}/values`,
|
||||
url: `/table-categories/${targetTable}/${targetColumn}/values`,
|
||||
});
|
||||
|
||||
const apiClient = (await import("@/lib/api/client")).apiClient;
|
||||
const response = await apiClient.get(`/table-categories/${tableConfig.selectedTable}/${columnName}/values`);
|
||||
const response = await apiClient.get(`/table-categories/${targetTable}/${targetColumn}/values`);
|
||||
|
||||
console.log(`📡 [TableList] API 응답 [${columnName}]:`, {
|
||||
success: response.data.success,
|
||||
|
|
@ -1000,6 +1015,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
});
|
||||
|
||||
if (Object.keys(mapping).length > 0) {
|
||||
// 🆕 원래 컬럼명(item_info.material)으로 매핑 저장
|
||||
mappings[columnName] = mapping;
|
||||
console.log(`✅ [TableList] 카테고리 매핑 로드 완료 [${columnName}]:`, {
|
||||
columnName,
|
||||
|
|
@ -1028,6 +1044,59 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
}
|
||||
}
|
||||
|
||||
// 🆕 엔티티 조인 컬럼 중 카테고리 타입이 아니지만 조인된 테이블의 카테고리 필드인 경우도 로드
|
||||
// 화면 설정의 columns에서 "테이블명.컬럼명" 형태의 조인 컬럼 추출
|
||||
const joinedColumns = tableConfig.columns
|
||||
?.filter((col) => col.columnName?.includes("."))
|
||||
.map((col) => col.columnName) || [];
|
||||
|
||||
// 알려진 카테고리 필드 목록 (조인된 테이블에서 자주 사용되는 카테고리 컬럼)
|
||||
const knownCategoryFields = ["material", "division", "status", "currency_code", "inbound_type", "outbound_type"];
|
||||
|
||||
for (const joinedColumn of joinedColumns) {
|
||||
// 이미 로드된 컬럼은 스킵
|
||||
if (mappings[joinedColumn]) continue;
|
||||
|
||||
const parts = joinedColumn.split(".");
|
||||
if (parts.length !== 2) continue;
|
||||
|
||||
const joinedTable = parts[0];
|
||||
const joinedColumnName = parts[1];
|
||||
|
||||
// 알려진 카테고리 필드인 경우만 로드 시도
|
||||
if (!knownCategoryFields.includes(joinedColumnName)) continue;
|
||||
|
||||
try {
|
||||
console.log(`📡 [TableList] 조인 테이블 카테고리 로드 시도 [${joinedColumn}]:`, {
|
||||
url: `/table-categories/${joinedTable}/${joinedColumnName}/values`,
|
||||
});
|
||||
|
||||
const response = await apiClient.get(`/table-categories/${joinedTable}/${joinedColumnName}/values`);
|
||||
|
||||
if (response.data.success && response.data.data && Array.isArray(response.data.data)) {
|
||||
const mapping: Record<string, { label: string; color?: string }> = {};
|
||||
|
||||
response.data.data.forEach((item: any) => {
|
||||
const key = String(item.valueCode);
|
||||
mapping[key] = {
|
||||
label: item.valueLabel,
|
||||
color: item.color,
|
||||
};
|
||||
});
|
||||
|
||||
if (Object.keys(mapping).length > 0) {
|
||||
mappings[joinedColumn] = mapping;
|
||||
console.log(`✅ [TableList] 조인 테이블 카테고리 매핑 로드 완료 [${joinedColumn}]:`, {
|
||||
mappingCount: Object.keys(mapping).length,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// 조인 테이블 카테고리 로드 실패는 무시 (카테고리가 아닌 필드일 수 있음)
|
||||
console.log(`ℹ️ [TableList] 조인 테이블 카테고리 없음 (${joinedColumn})`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log("📊 [TableList] 전체 카테고리 매핑 설정:", {
|
||||
mappingsCount: Object.keys(mappings).length,
|
||||
mappingsKeys: Object.keys(mappings),
|
||||
|
|
@ -1047,7 +1116,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
};
|
||||
|
||||
loadCategoryMappings();
|
||||
}, [tableConfig.selectedTable, categoryColumns.length, JSON.stringify(categoryColumns)]); // 더 명확한 의존성
|
||||
}, [tableConfig.selectedTable, categoryColumns.length, JSON.stringify(categoryColumns), JSON.stringify(tableConfig.columns)]); // 더 명확한 의존성
|
||||
|
||||
// ========================================
|
||||
// 데이터 가져오기
|
||||
|
|
@ -1885,7 +1954,18 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
if (inputType === "category") {
|
||||
if (!value) return "";
|
||||
|
||||
const mapping = categoryMappings[column.columnName];
|
||||
// 🆕 엔티티 조인 컬럼의 경우 여러 형태로 매핑 찾기
|
||||
// 1. 원래 컬럼명 (item_info.material)
|
||||
// 2. 점(.) 뒤의 컬럼명만 (material)
|
||||
let mapping = categoryMappings[column.columnName];
|
||||
|
||||
if (!mapping && column.columnName.includes(".")) {
|
||||
const simpleColumnName = column.columnName.split(".").pop();
|
||||
if (simpleColumnName) {
|
||||
mapping = categoryMappings[simpleColumnName];
|
||||
}
|
||||
}
|
||||
|
||||
const { Badge } = require("@/components/ui/badge");
|
||||
|
||||
// 다중 값 처리: 콤마로 구분된 값들을 분리
|
||||
|
|
|
|||
|
|
@ -265,7 +265,7 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
columnName: col.columnName || col.column_name,
|
||||
dataType: col.dataType || col.data_type || "text",
|
||||
label: col.displayName || col.columnLabel || col.column_label || col.columnName || col.column_name,
|
||||
}))
|
||||
})),
|
||||
);
|
||||
console.log("✅ 참조 테이블 컬럼 로드 완료:", columns.length, "개");
|
||||
}
|
||||
|
|
@ -511,7 +511,7 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
// 🎯 엔티티 컬럼의 표시 컬럼 정보 로드
|
||||
const loadEntityDisplayConfig = async (column: ColumnConfig) => {
|
||||
const configKey = `${column.columnName}`;
|
||||
|
||||
|
||||
// 이미 로드된 경우 스킵
|
||||
if (entityDisplayConfigs[configKey]) return;
|
||||
|
||||
|
|
@ -609,7 +609,7 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
// 기본 테이블 컬럼 정보는 항상 로드
|
||||
const sourceResult = await entityJoinApi.getReferenceTableColumns(sourceTable);
|
||||
const sourceColumns = sourceResult.columns || [];
|
||||
|
||||
|
||||
// joinTable이 있으면 조인 테이블 컬럼도 로드
|
||||
let joinColumns: Array<{ columnName: string; displayName: string; dataType: string }> = [];
|
||||
if (joinTable) {
|
||||
|
|
@ -761,9 +761,7 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
placeholder="테이블 제목 입력..."
|
||||
className="h-8 text-xs"
|
||||
/>
|
||||
<p className="text-[10px] text-muted-foreground">
|
||||
우선순위: 사용자 입력 제목 → 테이블 라벨명 → 테이블명
|
||||
</p>
|
||||
<p className="text-muted-foreground text-[10px]">우선순위: 사용자 입력 제목 → 테이블 라벨명 → 테이블명</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -782,7 +780,7 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
/>
|
||||
<Label htmlFor="checkboxEnabled">체크박스 표시</Label>
|
||||
</div>
|
||||
|
||||
|
||||
{config.checkbox?.enabled && (
|
||||
<>
|
||||
<div className="flex items-center space-x-2">
|
||||
|
|
@ -793,7 +791,7 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
/>
|
||||
<Label htmlFor="checkboxSelectAll">전체 선택 체크박스 표시</Label>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="checkboxPosition" className="text-xs">
|
||||
체크박스 위치
|
||||
|
|
@ -802,7 +800,7 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
id="checkboxPosition"
|
||||
value={config.checkbox?.position || "left"}
|
||||
onChange={(e) => handleNestedChange("checkbox", "position", e.target.value)}
|
||||
className="w-full h-8 text-xs border rounded-md px-2"
|
||||
className="h-8 w-full rounded-md border px-2 text-xs"
|
||||
>
|
||||
<option value="left">왼쪽</option>
|
||||
<option value="right">오른쪽</option>
|
||||
|
|
@ -913,7 +911,9 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
<CommandList>
|
||||
<CommandEmpty className="text-xs">컬럼을 찾을 수 없습니다.</CommandEmpty>
|
||||
{entityDisplayConfigs[column.columnName].sourceColumns.length > 0 && (
|
||||
<CommandGroup heading={`기본 테이블: ${column.entityDisplayConfig?.sourceTable || config.selectedTable || screenTableName}`}>
|
||||
<CommandGroup
|
||||
heading={`기본 테이블: ${column.entityDisplayConfig?.sourceTable || config.selectedTable || screenTableName}`}
|
||||
>
|
||||
{entityDisplayConfigs[column.columnName].sourceColumns.map((col) => (
|
||||
<CommandItem
|
||||
key={`source-${col.columnName}`}
|
||||
|
|
@ -966,11 +966,13 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
</div>
|
||||
|
||||
{/* 참조 테이블 미설정 안내 */}
|
||||
{!column.entityDisplayConfig?.joinTable && entityDisplayConfigs[column.columnName].sourceColumns.length > 0 && (
|
||||
<div className="rounded bg-blue-50 p-2 text-[10px] text-blue-600">
|
||||
현재 기본 테이블 컬럼만 표시됩니다. 테이블 타입 관리에서 참조 테이블을 설정하면 조인된 테이블의 컬럼도 선택할 수 있습니다.
|
||||
</div>
|
||||
)}
|
||||
{!column.entityDisplayConfig?.joinTable &&
|
||||
entityDisplayConfigs[column.columnName].sourceColumns.length > 0 && (
|
||||
<div className="rounded bg-blue-50 p-2 text-[10px] text-blue-600">
|
||||
현재 기본 테이블 컬럼만 표시됩니다. 테이블 타입 관리에서 참조 테이블을 설정하면 조인된
|
||||
테이블의 컬럼도 선택할 수 있습니다.
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 선택된 컬럼 미리보기 */}
|
||||
{entityDisplayConfigs[column.columnName].selectedColumns.length > 0 && (
|
||||
|
|
@ -1107,7 +1109,7 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
// 해당 컬럼의 input_type 확인
|
||||
const columnInfo = availableColumns.find((col) => col.columnName === column.columnName);
|
||||
const isNumberType = columnInfo?.input_type === "number" || columnInfo?.input_type === "decimal";
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
key={column.columnName}
|
||||
|
|
@ -1119,7 +1121,7 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
<span className="truncate text-xs" style={{ fontSize: "12px" }}>
|
||||
{columnInfo?.label || column.displayName || column.columnName}
|
||||
</span>
|
||||
|
||||
|
||||
{/* 숫자 타입인 경우 천단위 구분자 설정 */}
|
||||
{isNumberType && (
|
||||
<div className="flex items-center gap-1">
|
||||
|
|
@ -1131,9 +1133,9 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
}}
|
||||
className="h-3 w-3"
|
||||
/>
|
||||
<Label
|
||||
htmlFor={`thousand-sep-${column.columnName}`}
|
||||
className="text-[10px] text-muted-foreground cursor-pointer"
|
||||
<Label
|
||||
htmlFor={`thousand-sep-${column.columnName}`}
|
||||
className="text-muted-foreground cursor-pointer text-[10px]"
|
||||
>
|
||||
천단위 구분자
|
||||
</Label>
|
||||
|
|
@ -1147,8 +1149,7 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
checked={config.filter?.filters?.some((f) => f.columnName === column.columnName) || false}
|
||||
onCheckedChange={(checked) => {
|
||||
const currentFilters = config.filter?.filters || [];
|
||||
const columnLabel =
|
||||
columnInfo?.label || column.displayName || column.columnName;
|
||||
const columnLabel = columnInfo?.label || column.displayName || column.columnName;
|
||||
|
||||
if (checked) {
|
||||
// 필터 추가
|
||||
|
|
@ -1240,9 +1241,7 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
placeholder="40"
|
||||
className="h-8 text-xs"
|
||||
/>
|
||||
<p className="text-[10px] text-muted-foreground">
|
||||
기본값: 40px (0-200px 범위, 10px 단위 권장)
|
||||
</p>
|
||||
<p className="text-muted-foreground text-[10px]">기본값: 40px (0-200px 범위, 10px 단위 권장)</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -1251,19 +1250,20 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
<div className="space-y-3">
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold">데이터 필터링</h3>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
특정 컬럼 값으로 데이터를 필터링합니다
|
||||
</p>
|
||||
<p className="text-muted-foreground mt-1 text-xs">특정 컬럼 값으로 데이터를 필터링합니다</p>
|
||||
</div>
|
||||
<hr className="border-border" />
|
||||
<DataFilterConfigPanel
|
||||
tableName={config.selectedTable || screenTableName}
|
||||
columns={availableColumns.map((col) => ({
|
||||
columnName: col.columnName,
|
||||
columnLabel: col.label || col.columnName,
|
||||
dataType: col.dataType,
|
||||
input_type: col.input_type, // 🆕 실제 input_type 전달
|
||||
} as any))}
|
||||
columns={availableColumns.map(
|
||||
(col) =>
|
||||
({
|
||||
columnName: col.columnName,
|
||||
columnLabel: col.label || col.columnName,
|
||||
dataType: col.dataType,
|
||||
input_type: col.input_type, // 🆕 실제 input_type 전달
|
||||
}) as any,
|
||||
)}
|
||||
config={config.dataFilter}
|
||||
onConfigChange={(dataFilter) => handleChange("dataFilter", dataFilter)}
|
||||
/>
|
||||
|
|
@ -1273,12 +1273,12 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
<div className="space-y-3">
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold">연결된 필터</h3>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
<p className="text-muted-foreground mt-1 text-xs">
|
||||
셀렉트박스 등 다른 컴포넌트의 값으로 테이블 데이터를 실시간 필터링합니다
|
||||
</p>
|
||||
</div>
|
||||
<hr className="border-border" />
|
||||
|
||||
|
||||
{/* 연결된 필터 목록 */}
|
||||
<div className="space-y-2">
|
||||
{(config.linkedFilters || []).map((filter, index) => (
|
||||
|
|
@ -1293,16 +1293,12 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
newFilters[index] = { ...filter, sourceComponentId: e.target.value };
|
||||
handleChange("linkedFilters", newFilters);
|
||||
}}
|
||||
className="h-7 text-xs flex-1"
|
||||
className="h-7 flex-1 text-xs"
|
||||
/>
|
||||
<span className="text-xs text-muted-foreground">→</span>
|
||||
<span className="text-muted-foreground text-xs">→</span>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
className="h-7 flex-1 justify-between text-xs"
|
||||
>
|
||||
<Button variant="outline" role="combobox" className="h-7 flex-1 justify-between text-xs">
|
||||
{filter.targetColumn || "필터링할 컬럼 선택"}
|
||||
<ChevronsUpDown className="ml-2 h-3 w-3 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
|
|
@ -1311,7 +1307,7 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
<Command>
|
||||
<CommandInput placeholder="컬럼 검색..." className="h-8 text-xs" />
|
||||
<CommandList>
|
||||
<CommandEmpty className="text-xs py-2">컬럼을 찾을 수 없습니다</CommandEmpty>
|
||||
<CommandEmpty className="py-2 text-xs">컬럼을 찾을 수 없습니다</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{availableColumns.map((col) => (
|
||||
<CommandItem
|
||||
|
|
@ -1327,7 +1323,7 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-3 w-3",
|
||||
filter.targetColumn === col.columnName ? "opacity-100" : "opacity-0"
|
||||
filter.targetColumn === col.columnName ? "opacity-100" : "opacity-0",
|
||||
)}
|
||||
/>
|
||||
{col.label || col.columnName}
|
||||
|
|
@ -1353,7 +1349,7 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
</Button>
|
||||
</div>
|
||||
))}
|
||||
|
||||
|
||||
{/* 연결된 필터 추가 버튼 */}
|
||||
<Button
|
||||
variant="outline"
|
||||
|
|
@ -1361,7 +1357,7 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
onClick={() => {
|
||||
const newFilters = [
|
||||
...(config.linkedFilters || []),
|
||||
{ sourceComponentId: "", targetColumn: "", operator: "equals" as const, enabled: true }
|
||||
{ sourceComponentId: "", targetColumn: "", operator: "equals" as const, enabled: true },
|
||||
];
|
||||
handleChange("linkedFilters", newFilters);
|
||||
}}
|
||||
|
|
@ -1370,8 +1366,8 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
<Plus className="mr-1 h-3 w-3" />
|
||||
연결된 필터 추가
|
||||
</Button>
|
||||
|
||||
<p className="text-[10px] text-muted-foreground">
|
||||
|
||||
<p className="text-muted-foreground text-[10px]">
|
||||
예: 셀렉트박스(ID: select-basic-123)의 값으로 테이블의 inbound_type 컬럼을 필터링
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -1381,12 +1377,12 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
<div className="space-y-3">
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold">제외 필터</h3>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
<p className="text-muted-foreground mt-1 text-xs">
|
||||
다른 테이블에 이미 존재하는 데이터를 목록에서 제외합니다
|
||||
</p>
|
||||
</div>
|
||||
<hr className="border-border" />
|
||||
|
||||
|
||||
{/* 제외 필터 활성화 */}
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
|
|
@ -1403,7 +1399,7 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
제외 필터 활성화
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
|
||||
{config.excludeFilter?.enabled && (
|
||||
<div className="space-y-3 rounded border p-3">
|
||||
{/* 참조 테이블 선택 */}
|
||||
|
|
@ -1411,11 +1407,7 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
<Label className="text-xs font-medium">참조 테이블 (매핑 테이블)</Label>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
className="h-8 w-full justify-between text-xs"
|
||||
>
|
||||
<Button variant="outline" role="combobox" className="h-8 w-full justify-between text-xs">
|
||||
{config.excludeFilter?.referenceTable || "테이블 선택..."}
|
||||
<ChevronsUpDown className="ml-2 h-3 w-3 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
|
|
@ -1424,7 +1416,7 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
<Command>
|
||||
<CommandInput placeholder="테이블 검색..." className="h-8 text-xs" />
|
||||
<CommandList>
|
||||
<CommandEmpty className="text-xs py-2">테이블을 찾을 수 없습니다</CommandEmpty>
|
||||
<CommandEmpty className="py-2 text-xs">테이블을 찾을 수 없습니다</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{availableTables.map((table) => (
|
||||
<CommandItem
|
||||
|
|
@ -1445,7 +1437,9 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-3 w-3",
|
||||
config.excludeFilter?.referenceTable === table.tableName ? "opacity-100" : "opacity-0"
|
||||
config.excludeFilter?.referenceTable === table.tableName
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
)}
|
||||
/>
|
||||
{table.displayName || table.tableName}
|
||||
|
|
@ -1457,7 +1451,7 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
|
||||
{config.excludeFilter?.referenceTable && (
|
||||
<>
|
||||
{/* 비교 컬럼 설정 - 한 줄에 두 개 */}
|
||||
|
|
@ -1473,9 +1467,7 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
disabled={loadingReferenceColumns}
|
||||
className="h-8 w-full justify-between text-xs"
|
||||
>
|
||||
{loadingReferenceColumns
|
||||
? "..."
|
||||
: config.excludeFilter?.referenceColumn || "선택"}
|
||||
{loadingReferenceColumns ? "..." : config.excludeFilter?.referenceColumn || "선택"}
|
||||
<ChevronsUpDown className="ml-1 h-3 w-3 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
|
|
@ -1483,7 +1475,7 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
<Command>
|
||||
<CommandInput placeholder="검색..." className="h-8 text-xs" />
|
||||
<CommandList>
|
||||
<CommandEmpty className="text-xs py-2">없음</CommandEmpty>
|
||||
<CommandEmpty className="py-2 text-xs">없음</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{referenceTableColumns.map((col) => (
|
||||
<CommandItem
|
||||
|
|
@ -1500,7 +1492,9 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-3 w-3",
|
||||
config.excludeFilter?.referenceColumn === col.columnName ? "opacity-100" : "opacity-0"
|
||||
config.excludeFilter?.referenceColumn === col.columnName
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
)}
|
||||
/>
|
||||
{col.label || col.columnName}
|
||||
|
|
@ -1512,17 +1506,13 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
|
||||
{/* 소스 컬럼 (현재 테이블) */}
|
||||
<div className="space-y-1">
|
||||
<Label className="text-xs">비교 컬럼 (현재)</Label>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
className="h-8 w-full justify-between text-xs"
|
||||
>
|
||||
<Button variant="outline" role="combobox" className="h-8 w-full justify-between text-xs">
|
||||
{config.excludeFilter?.sourceColumn || "선택"}
|
||||
<ChevronsUpDown className="ml-1 h-3 w-3 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
|
|
@ -1531,7 +1521,7 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
<Command>
|
||||
<CommandInput placeholder="검색..." className="h-8 text-xs" />
|
||||
<CommandList>
|
||||
<CommandEmpty className="text-xs py-2">없음</CommandEmpty>
|
||||
<CommandEmpty className="py-2 text-xs">없음</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{availableColumns.map((col) => (
|
||||
<CommandItem
|
||||
|
|
@ -1548,7 +1538,9 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-3 w-3",
|
||||
config.excludeFilter?.sourceColumn === col.columnName ? "opacity-100" : "opacity-0"
|
||||
config.excludeFilter?.sourceColumn === col.columnName
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
)}
|
||||
/>
|
||||
{col.label || col.columnName}
|
||||
|
|
@ -1561,11 +1553,11 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* 조건 필터 - 특정 조건의 데이터만 제외 */}
|
||||
<div className="space-y-1">
|
||||
<Label className="text-xs">조건 필터 (선택사항)</Label>
|
||||
<p className="text-[10px] text-muted-foreground mb-1">
|
||||
<p className="text-muted-foreground mb-1 text-[10px]">
|
||||
특정 조건의 데이터만 제외하려면 설정하세요 (예: 특정 거래처의 품목만)
|
||||
</p>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
|
|
@ -1578,9 +1570,9 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
disabled={loadingReferenceColumns}
|
||||
className="h-8 w-full justify-between text-xs"
|
||||
>
|
||||
{loadingReferenceColumns
|
||||
? "..."
|
||||
: config.excludeFilter?.filterColumn
|
||||
{loadingReferenceColumns
|
||||
? "..."
|
||||
: config.excludeFilter?.filterColumn
|
||||
? `매핑: ${config.excludeFilter.filterColumn}`
|
||||
: "매핑 테이블 컬럼"}
|
||||
<ChevronsUpDown className="ml-1 h-3 w-3 shrink-0 opacity-50" />
|
||||
|
|
@ -1590,7 +1582,7 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
<Command>
|
||||
<CommandInput placeholder="검색..." className="h-8 text-xs" />
|
||||
<CommandList>
|
||||
<CommandEmpty className="text-xs py-2">없음</CommandEmpty>
|
||||
<CommandEmpty className="py-2 text-xs">없음</CommandEmpty>
|
||||
<CommandGroup>
|
||||
<CommandItem
|
||||
value=""
|
||||
|
|
@ -1601,9 +1593,14 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
filterValueField: undefined,
|
||||
});
|
||||
}}
|
||||
className="text-xs text-muted-foreground"
|
||||
className="text-muted-foreground text-xs"
|
||||
>
|
||||
<Check className={cn("mr-2 h-3 w-3", !config.excludeFilter?.filterColumn ? "opacity-100" : "opacity-0")} />
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-3 w-3",
|
||||
!config.excludeFilter?.filterColumn ? "opacity-100" : "opacity-0",
|
||||
)}
|
||||
/>
|
||||
사용 안함
|
||||
</CommandItem>
|
||||
{referenceTableColumns.map((col) => (
|
||||
|
|
@ -1624,7 +1621,9 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-3 w-3",
|
||||
config.excludeFilter?.filterColumn === col.columnName ? "opacity-100" : "opacity-0"
|
||||
config.excludeFilter?.filterColumn === col.columnName
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
)}
|
||||
/>
|
||||
{col.label || col.columnName}
|
||||
|
|
@ -1635,7 +1634,7 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
|
||||
{/* 필터 값 필드명 (부모 화면에서 전달받는 필드) */}
|
||||
<Input
|
||||
placeholder="예: customer_code"
|
||||
|
|
@ -1653,18 +1652,24 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
|||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
{/* 설정 요약 */}
|
||||
{config.excludeFilter?.referenceTable && config.excludeFilter?.referenceColumn && config.excludeFilter?.sourceColumn && (
|
||||
<div className="rounded bg-muted/50 p-2 text-[10px] text-muted-foreground">
|
||||
<strong>설정 요약:</strong> {config.selectedTable || screenTableName}.{config.excludeFilter.sourceColumn} 가
|
||||
{" "}{config.excludeFilter.referenceTable}.{config.excludeFilter.referenceColumn} 에
|
||||
{config.excludeFilter.filterColumn && config.excludeFilter.filterValueField && (
|
||||
<> ({config.excludeFilter.filterColumn}=URL의 {config.excludeFilter.filterValueField}일 때)</>
|
||||
)}
|
||||
{" "}이미 있으면 제외
|
||||
</div>
|
||||
)}
|
||||
{config.excludeFilter?.referenceTable &&
|
||||
config.excludeFilter?.referenceColumn &&
|
||||
config.excludeFilter?.sourceColumn && (
|
||||
<div className="bg-muted/50 text-muted-foreground rounded p-2 text-[10px]">
|
||||
<strong>설정 요약:</strong> {config.selectedTable || screenTableName}.
|
||||
{config.excludeFilter.sourceColumn} 가 {config.excludeFilter.referenceTable}.
|
||||
{config.excludeFilter.referenceColumn} 에
|
||||
{config.excludeFilter.filterColumn && config.excludeFilter.filterValueField && (
|
||||
<>
|
||||
{" "}
|
||||
({config.excludeFilter.filterColumn}=URL의 {config.excludeFilter.filterValueField}일 때)
|
||||
</>
|
||||
)}{" "}
|
||||
이미 있으면 제외
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1680,3 +1680,4 @@ const 출고등록_설정: ScreenSplitPanel = {
|
|||
화면 임베딩 및 데이터 전달 시스템은 복잡한 업무 워크플로우를 효율적으로 처리할 수 있는 강력한 기능입니다. 단계별로 체계적으로 구현하면 약 3.5개월 내에 완성할 수 있으며, 이를 통해 사용자 경험을 크게 향상시킬 수 있습니다.
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -527,3 +527,4 @@ const { data: config } = await getScreenSplitPanel(screenId);
|
|||
이제 입고 등록과 같은 복잡한 워크플로우를 구현할 수 있습니다. 다음 단계는 각 컴포넌트 타입별 DataReceivable 인터페이스 구현과 설정 UI 개발입니다.
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -514,3 +514,4 @@ function ScreenViewPage() {
|
|||
새로운 시스템은 기존 시스템과 **독립적으로 동작**하며, 최소한의 수정만으로 통합 가능합니다. 화면 페이지에 조건 분기만 추가하면 바로 사용할 수 있습니다.
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue