- Replaced existing toast error messages with the new `showErrorToast` utility across multiple components, improving consistency in error reporting. - Updated error messages to provide more specific guidance for users, enhancing the overall user experience during error scenarios. - Ensured that all relevant error handling in batch management, external call configurations, cascading management, and screen management components now utilizes the new utility for better maintainability.
628 lines
22 KiB
TypeScript
628 lines
22 KiB
TypeScript
"use client";
|
|
|
|
import React, { useState, useEffect, useCallback } from "react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Label } from "@/components/ui/label";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@/components/ui/select";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from "@/components/ui/dialog";
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from "@/components/ui/card";
|
|
import { Plus, Pencil, Trash2, Database, RefreshCw, Layers } from "lucide-react";
|
|
import { toast } from "sonner";
|
|
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
import { LoadingSpinner } from "@/components/common/LoadingSpinner";
|
|
import {
|
|
hierarchyColumnApi,
|
|
HierarchyColumnGroup,
|
|
CreateHierarchyGroupRequest,
|
|
} from "@/lib/api/hierarchyColumn";
|
|
import { commonCodeApi } from "@/lib/api/commonCode";
|
|
import apiClient from "@/lib/api/client";
|
|
|
|
interface TableInfo {
|
|
tableName: string;
|
|
displayName?: string;
|
|
}
|
|
|
|
interface ColumnInfo {
|
|
columnName: string;
|
|
displayName?: string;
|
|
dataType?: string;
|
|
}
|
|
|
|
interface CategoryInfo {
|
|
categoryCode: string;
|
|
categoryName: string;
|
|
}
|
|
|
|
export default function HierarchyColumnTab() {
|
|
// 상태
|
|
const [groups, setGroups] = useState<HierarchyColumnGroup[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [modalOpen, setModalOpen] = useState(false);
|
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
|
const [selectedGroup, setSelectedGroup] = useState<HierarchyColumnGroup | null>(null);
|
|
const [isEditing, setIsEditing] = useState(false);
|
|
|
|
// 폼 상태
|
|
const [formData, setFormData] = useState({
|
|
groupCode: "",
|
|
groupName: "",
|
|
description: "",
|
|
codeCategory: "",
|
|
tableName: "",
|
|
maxDepth: 3,
|
|
mappings: [
|
|
{ depth: 1, levelLabel: "대분류", columnName: "", placeholder: "대분류 선택", isRequired: true },
|
|
{ depth: 2, levelLabel: "중분류", columnName: "", placeholder: "중분류 선택", isRequired: false },
|
|
{ depth: 3, levelLabel: "소분류", columnName: "", placeholder: "소분류 선택", isRequired: false },
|
|
],
|
|
});
|
|
|
|
// 참조 데이터
|
|
const [tables, setTables] = useState<TableInfo[]>([]);
|
|
const [columns, setColumns] = useState<ColumnInfo[]>([]);
|
|
const [categories, setCategories] = useState<CategoryInfo[]>([]);
|
|
const [loadingTables, setLoadingTables] = useState(false);
|
|
const [loadingColumns, setLoadingColumns] = useState(false);
|
|
const [loadingCategories, setLoadingCategories] = useState(false);
|
|
|
|
// 그룹 목록 로드
|
|
const loadGroups = useCallback(async () => {
|
|
setLoading(true);
|
|
try {
|
|
const response = await hierarchyColumnApi.getAll();
|
|
if (response.success && response.data) {
|
|
setGroups(response.data);
|
|
} else {
|
|
toast.error(response.error || "계층구조 그룹 로드 실패");
|
|
}
|
|
} catch (error) {
|
|
console.error("계층구조 그룹 로드 에러:", error);
|
|
toast.error("계층구조 그룹을 로드하는 중 오류가 발생했습니다.");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
// 테이블 목록 로드
|
|
const loadTables = useCallback(async () => {
|
|
setLoadingTables(true);
|
|
try {
|
|
const response = await apiClient.get("/table-management/tables");
|
|
if (response.data?.success && response.data?.data) {
|
|
setTables(response.data.data);
|
|
}
|
|
} catch (error) {
|
|
console.error("테이블 로드 에러:", error);
|
|
} finally {
|
|
setLoadingTables(false);
|
|
}
|
|
}, []);
|
|
|
|
// 카테고리 목록 로드
|
|
const loadCategories = useCallback(async () => {
|
|
setLoadingCategories(true);
|
|
try {
|
|
const response = await commonCodeApi.categories.getList();
|
|
if (response.success && response.data) {
|
|
setCategories(
|
|
response.data.map((cat: any) => ({
|
|
categoryCode: cat.categoryCode || cat.category_code,
|
|
categoryName: cat.categoryName || cat.category_name,
|
|
}))
|
|
);
|
|
}
|
|
} catch (error) {
|
|
console.error("카테고리 로드 에러:", error);
|
|
} finally {
|
|
setLoadingCategories(false);
|
|
}
|
|
}, []);
|
|
|
|
// 테이블 선택 시 컬럼 로드
|
|
const loadColumns = useCallback(async (tableName: string) => {
|
|
if (!tableName) {
|
|
setColumns([]);
|
|
return;
|
|
}
|
|
setLoadingColumns(true);
|
|
try {
|
|
const response = await apiClient.get(`/table-management/tables/${tableName}/columns`);
|
|
if (response.data?.success && response.data?.data) {
|
|
setColumns(response.data.data);
|
|
}
|
|
} catch (error) {
|
|
console.error("컬럼 로드 에러:", error);
|
|
} finally {
|
|
setLoadingColumns(false);
|
|
}
|
|
}, []);
|
|
|
|
// 초기 로드
|
|
useEffect(() => {
|
|
loadGroups();
|
|
loadTables();
|
|
loadCategories();
|
|
}, [loadGroups, loadTables, loadCategories]);
|
|
|
|
// 테이블 선택 변경 시 컬럼 로드
|
|
useEffect(() => {
|
|
if (formData.tableName) {
|
|
loadColumns(formData.tableName);
|
|
}
|
|
}, [formData.tableName, loadColumns]);
|
|
|
|
// 폼 초기화
|
|
const resetForm = () => {
|
|
setFormData({
|
|
groupCode: "",
|
|
groupName: "",
|
|
description: "",
|
|
codeCategory: "",
|
|
tableName: "",
|
|
maxDepth: 3,
|
|
mappings: [
|
|
{ depth: 1, levelLabel: "대분류", columnName: "", placeholder: "대분류 선택", isRequired: true },
|
|
{ depth: 2, levelLabel: "중분류", columnName: "", placeholder: "중분류 선택", isRequired: false },
|
|
{ depth: 3, levelLabel: "소분류", columnName: "", placeholder: "소분류 선택", isRequired: false },
|
|
],
|
|
});
|
|
setSelectedGroup(null);
|
|
setIsEditing(false);
|
|
};
|
|
|
|
// 모달 열기 (신규)
|
|
const openCreateModal = () => {
|
|
resetForm();
|
|
setModalOpen(true);
|
|
};
|
|
|
|
// 모달 열기 (수정)
|
|
const openEditModal = (group: HierarchyColumnGroup) => {
|
|
setSelectedGroup(group);
|
|
setIsEditing(true);
|
|
|
|
// 매핑 데이터 변환
|
|
const mappings = [1, 2, 3].map((depth) => {
|
|
const existing = group.mappings?.find((m) => m.depth === depth);
|
|
return {
|
|
depth,
|
|
levelLabel: existing?.level_label || (depth === 1 ? "대분류" : depth === 2 ? "중분류" : "소분류"),
|
|
columnName: existing?.column_name || "",
|
|
placeholder: existing?.placeholder || `${depth === 1 ? "대분류" : depth === 2 ? "중분류" : "소분류"} 선택`,
|
|
isRequired: existing?.is_required === "Y",
|
|
};
|
|
});
|
|
|
|
setFormData({
|
|
groupCode: group.group_code,
|
|
groupName: group.group_name,
|
|
description: group.description || "",
|
|
codeCategory: group.code_category,
|
|
tableName: group.table_name,
|
|
maxDepth: group.max_depth,
|
|
mappings,
|
|
});
|
|
|
|
// 컬럼 로드
|
|
loadColumns(group.table_name);
|
|
setModalOpen(true);
|
|
};
|
|
|
|
// 삭제 확인 열기
|
|
const openDeleteDialog = (group: HierarchyColumnGroup) => {
|
|
setSelectedGroup(group);
|
|
setDeleteDialogOpen(true);
|
|
};
|
|
|
|
// 저장
|
|
const handleSave = async () => {
|
|
// 필수 필드 검증
|
|
if (!formData.groupCode || !formData.groupName || !formData.codeCategory || !formData.tableName) {
|
|
toast.error("필수 필드를 모두 입력해주세요.");
|
|
return;
|
|
}
|
|
|
|
// 최소 1개 컬럼 매핑 검증
|
|
const validMappings = formData.mappings
|
|
.filter((m) => m.depth <= formData.maxDepth && m.columnName)
|
|
.map((m) => ({
|
|
depth: m.depth,
|
|
levelLabel: m.levelLabel,
|
|
columnName: m.columnName,
|
|
placeholder: m.placeholder,
|
|
isRequired: m.isRequired,
|
|
}));
|
|
|
|
if (validMappings.length === 0) {
|
|
toast.error("최소 하나의 컬럼 매핑이 필요합니다.");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
if (isEditing && selectedGroup) {
|
|
// 수정
|
|
const response = await hierarchyColumnApi.update(selectedGroup.group_id, {
|
|
groupName: formData.groupName,
|
|
description: formData.description,
|
|
maxDepth: formData.maxDepth,
|
|
mappings: validMappings,
|
|
});
|
|
|
|
if (response.success) {
|
|
toast.success("계층구조 그룹이 수정되었습니다.");
|
|
setModalOpen(false);
|
|
loadGroups();
|
|
} else {
|
|
toast.error(response.error || "수정 실패");
|
|
}
|
|
} else {
|
|
// 생성
|
|
const request: CreateHierarchyGroupRequest = {
|
|
groupCode: formData.groupCode,
|
|
groupName: formData.groupName,
|
|
description: formData.description,
|
|
codeCategory: formData.codeCategory,
|
|
tableName: formData.tableName,
|
|
maxDepth: formData.maxDepth,
|
|
mappings: validMappings,
|
|
};
|
|
|
|
const response = await hierarchyColumnApi.create(request);
|
|
|
|
if (response.success) {
|
|
toast.success("계층구조 그룹이 생성되었습니다.");
|
|
setModalOpen(false);
|
|
loadGroups();
|
|
} else {
|
|
toast.error(response.error || "생성 실패");
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error("저장 에러:", error);
|
|
showErrorToast("계층구조 설정 저장에 실패했습니다", error, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
|
}
|
|
};
|
|
|
|
// 삭제
|
|
const handleDelete = async () => {
|
|
if (!selectedGroup) return;
|
|
|
|
try {
|
|
const response = await hierarchyColumnApi.delete(selectedGroup.group_id);
|
|
if (response.success) {
|
|
toast.success("계층구조 그룹이 삭제되었습니다.");
|
|
setDeleteDialogOpen(false);
|
|
loadGroups();
|
|
} else {
|
|
toast.error(response.error || "삭제 실패");
|
|
}
|
|
} catch (error) {
|
|
console.error("삭제 에러:", error);
|
|
showErrorToast("계층구조 설정 삭제에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
|
}
|
|
};
|
|
|
|
// 매핑 컬럼 변경
|
|
const handleMappingChange = (depth: number, field: string, value: any) => {
|
|
setFormData((prev) => ({
|
|
...prev,
|
|
mappings: prev.mappings.map((m) =>
|
|
m.depth === depth ? { ...m, [field]: value } : m
|
|
),
|
|
}));
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* 헤더 */}
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h2 className="text-xl font-semibold">계층구조 컬럼 그룹</h2>
|
|
<p className="text-sm text-muted-foreground">
|
|
공통코드 계층구조를 테이블 컬럼에 매핑하여 대분류/중분류/소분류를 각각 별도 컬럼에 저장합니다.
|
|
</p>
|
|
</div>
|
|
<div className="flex gap-2">
|
|
<Button variant="outline" size="sm" onClick={loadGroups} disabled={loading}>
|
|
<RefreshCw className={`mr-2 h-4 w-4 ${loading ? "animate-spin" : ""}`} />
|
|
새로고침
|
|
</Button>
|
|
<Button size="sm" onClick={openCreateModal}>
|
|
<Plus className="mr-2 h-4 w-4" />
|
|
새 그룹 추가
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 그룹 목록 */}
|
|
{loading ? (
|
|
<div className="flex items-center justify-center py-12">
|
|
<LoadingSpinner />
|
|
<span className="ml-2 text-muted-foreground">로딩 중...</span>
|
|
</div>
|
|
) : groups.length === 0 ? (
|
|
<Card>
|
|
<CardContent className="flex flex-col items-center justify-center py-12">
|
|
<Layers className="h-12 w-12 text-muted-foreground" />
|
|
<p className="mt-4 text-muted-foreground">계층구조 컬럼 그룹이 없습니다.</p>
|
|
<Button className="mt-4" onClick={openCreateModal}>
|
|
<Plus className="mr-2 h-4 w-4" />
|
|
첫 번째 그룹 만들기
|
|
</Button>
|
|
</CardContent>
|
|
</Card>
|
|
) : (
|
|
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
|
{groups.map((group) => (
|
|
<Card key={group.group_id} className="hover:shadow-md transition-shadow">
|
|
<CardHeader className="pb-3">
|
|
<div className="flex items-start justify-between">
|
|
<div>
|
|
<CardTitle className="text-base">{group.group_name}</CardTitle>
|
|
<CardDescription className="text-xs">{group.group_code}</CardDescription>
|
|
</div>
|
|
<div className="flex gap-1">
|
|
<Button variant="ghost" size="icon" className="h-8 w-8" onClick={() => openEditModal(group)}>
|
|
<Pencil className="h-4 w-4" />
|
|
</Button>
|
|
<Button variant="ghost" size="icon" className="h-8 w-8 text-destructive" onClick={() => openDeleteDialog(group)}>
|
|
<Trash2 className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent className="space-y-3">
|
|
<div className="flex items-center gap-2 text-sm">
|
|
<Database className="h-4 w-4 text-muted-foreground" />
|
|
<span className="font-medium">{group.table_name}</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<Badge variant="outline">{group.code_category}</Badge>
|
|
<Badge variant="secondary">{group.max_depth}단계</Badge>
|
|
</div>
|
|
{group.mappings && group.mappings.length > 0 && (
|
|
<div className="space-y-1">
|
|
{group.mappings.map((mapping) => (
|
|
<div key={mapping.depth} className="flex items-center gap-2 text-xs">
|
|
<Badge variant="outline" className="w-14 justify-center">
|
|
{mapping.level_label}
|
|
</Badge>
|
|
<span className="font-mono text-muted-foreground">{mapping.column_name}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{/* 생성/수정 모달 */}
|
|
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
|
|
<DialogContent className="max-w-[600px]">
|
|
<DialogHeader>
|
|
<DialogTitle>{isEditing ? "계층구조 그룹 수정" : "계층구조 그룹 생성"}</DialogTitle>
|
|
<DialogDescription>
|
|
공통코드 계층구조를 테이블 컬럼에 매핑합니다.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<div className="space-y-4 py-4">
|
|
{/* 기본 정보 */}
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="space-y-2">
|
|
<Label>그룹 코드 *</Label>
|
|
<Input
|
|
value={formData.groupCode}
|
|
onChange={(e) => setFormData({ ...formData, groupCode: e.target.value.toUpperCase() })}
|
|
placeholder="예: ITEM_CAT_HIERARCHY"
|
|
disabled={isEditing}
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label>그룹명 *</Label>
|
|
<Input
|
|
value={formData.groupName}
|
|
onChange={(e) => setFormData({ ...formData, groupName: e.target.value })}
|
|
placeholder="예: 품목분류 계층"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label>설명</Label>
|
|
<Input
|
|
value={formData.description}
|
|
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
|
placeholder="계층구조에 대한 설명"
|
|
/>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="space-y-2">
|
|
<Label>공통코드 카테고리 *</Label>
|
|
<Select
|
|
value={formData.codeCategory}
|
|
onValueChange={(value) => setFormData({ ...formData, codeCategory: value })}
|
|
disabled={isEditing}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="카테고리 선택" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{loadingCategories ? (
|
|
<SelectItem value="_loading" disabled>로딩 중...</SelectItem>
|
|
) : (
|
|
categories.map((cat) => (
|
|
<SelectItem key={cat.categoryCode} value={cat.categoryCode}>
|
|
{cat.categoryName} ({cat.categoryCode})
|
|
</SelectItem>
|
|
))
|
|
)}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label>적용 테이블 *</Label>
|
|
<Select
|
|
value={formData.tableName}
|
|
onValueChange={(value) => setFormData({ ...formData, tableName: value })}
|
|
disabled={isEditing}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="테이블 선택" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{loadingTables ? (
|
|
<SelectItem value="_loading" disabled>로딩 중...</SelectItem>
|
|
) : (
|
|
tables.map((table) => (
|
|
<SelectItem key={table.tableName} value={table.tableName}>
|
|
{table.displayName || table.tableName}
|
|
</SelectItem>
|
|
))
|
|
)}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label>최대 깊이</Label>
|
|
<Select
|
|
value={String(formData.maxDepth)}
|
|
onValueChange={(value) => setFormData({ ...formData, maxDepth: Number(value) })}
|
|
>
|
|
<SelectTrigger className="w-40">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="1">1단계 (대분류만)</SelectItem>
|
|
<SelectItem value="2">2단계 (대/중분류)</SelectItem>
|
|
<SelectItem value="3">3단계 (대/중/소분류)</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
{/* 컬럼 매핑 */}
|
|
<div className="space-y-3 border-t pt-4">
|
|
<Label className="text-base font-medium">컬럼 매핑</Label>
|
|
<p className="text-xs text-muted-foreground">
|
|
각 계층 레벨에 저장할 컬럼을 선택합니다.
|
|
</p>
|
|
|
|
{formData.mappings
|
|
.filter((m) => m.depth <= formData.maxDepth)
|
|
.map((mapping) => (
|
|
<div key={mapping.depth} className="grid grid-cols-4 gap-2 items-center">
|
|
<div className="flex items-center gap-2">
|
|
<Badge variant={mapping.depth === 1 ? "default" : "outline"}>
|
|
{mapping.depth}단계
|
|
</Badge>
|
|
<Input
|
|
value={mapping.levelLabel}
|
|
onChange={(e) => handleMappingChange(mapping.depth, "levelLabel", e.target.value)}
|
|
className="h-8 text-xs"
|
|
placeholder="라벨"
|
|
/>
|
|
</div>
|
|
<Select
|
|
value={mapping.columnName || "_none"}
|
|
onValueChange={(value) => handleMappingChange(mapping.depth, "columnName", value === "_none" ? "" : value)}
|
|
>
|
|
<SelectTrigger className="h-8 text-xs">
|
|
<SelectValue placeholder="컬럼 선택" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="_none">컬럼 선택</SelectItem>
|
|
{loadingColumns ? (
|
|
<SelectItem value="_loading" disabled>로딩 중...</SelectItem>
|
|
) : (
|
|
columns.map((col) => (
|
|
<SelectItem key={col.columnName} value={col.columnName}>
|
|
{col.displayName || col.columnName}
|
|
</SelectItem>
|
|
))
|
|
)}
|
|
</SelectContent>
|
|
</Select>
|
|
<Input
|
|
value={mapping.placeholder}
|
|
onChange={(e) => handleMappingChange(mapping.depth, "placeholder", e.target.value)}
|
|
className="h-8 text-xs"
|
|
placeholder="플레이스홀더"
|
|
/>
|
|
<div className="flex items-center gap-2">
|
|
<input
|
|
type="checkbox"
|
|
checked={mapping.isRequired}
|
|
onChange={(e) => handleMappingChange(mapping.depth, "isRequired", e.target.checked)}
|
|
className="h-4 w-4"
|
|
/>
|
|
<span className="text-xs text-muted-foreground">필수</span>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<DialogFooter>
|
|
<Button variant="outline" onClick={() => setModalOpen(false)}>
|
|
취소
|
|
</Button>
|
|
<Button onClick={handleSave}>
|
|
{isEditing ? "수정" : "생성"}
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
{/* 삭제 확인 다이얼로그 */}
|
|
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
|
<DialogContent className="max-w-md">
|
|
<DialogHeader>
|
|
<DialogTitle>계층구조 그룹 삭제</DialogTitle>
|
|
<DialogDescription>
|
|
"{selectedGroup?.group_name}" 그룹을 삭제하시겠습니까?
|
|
<br />
|
|
이 작업은 되돌릴 수 없습니다.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<DialogFooter>
|
|
<Button variant="outline" onClick={() => setDeleteDialogOpen(false)}>
|
|
취소
|
|
</Button>
|
|
<Button variant="destructive" onClick={handleDelete}>
|
|
삭제
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</div>
|
|
);
|
|
}
|
|
|