feat: 테이블 타입 관리 페이지 UI 개선 및 테이블 삭제 방식 변경
- 테이블 삭제 방식을 체크박스 선택 기반 일괄 삭제로 변경 - 좌측 테이블 리스트 영역에 스크롤 적용 - 선택된 테이블에 검정 테두리 표시 (border-2 border-black) - 우측 상단 타이틀 제거 - 각 테이블 카드에 라운딩 적용 (rounded-lg) - 컬럼 간 간격 개선 (입력 타입-상세 설정 간격 증가) - Entity 설정 박스 스타일 제거 (평면적 레이아웃으로 변경) - 좌측 영역 우측 여백 조정 (pr-4)
This commit is contained in:
parent
21af6c5c17
commit
4924fbe71d
|
|
@ -353,14 +353,14 @@ export default function BatchManagementPage() {
|
|||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>작업명</TableHead>
|
||||
<TableHead>타입</TableHead>
|
||||
<TableHead>스케줄</TableHead>
|
||||
<TableHead>상태</TableHead>
|
||||
<TableHead>실행 통계</TableHead>
|
||||
<TableHead>성공률</TableHead>
|
||||
<TableHead>마지막 실행</TableHead>
|
||||
<TableHead>작업</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">작업명</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">타입</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">스케줄</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">상태</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">실행 통계</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">성공률</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">마지막 실행</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">작업</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
|
|
|
|||
|
|
@ -249,20 +249,20 @@ export default function CollectionManagementPage() {
|
|||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>설정명</TableHead>
|
||||
<TableHead>수집 타입</TableHead>
|
||||
<TableHead>소스 테이블</TableHead>
|
||||
<TableHead>대상 테이블</TableHead>
|
||||
<TableHead>스케줄</TableHead>
|
||||
<TableHead>상태</TableHead>
|
||||
<TableHead>마지막 수집</TableHead>
|
||||
<TableHead>작업</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">설정명</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">수집 타입</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">소스 테이블</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">대상 테이블</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">스케줄</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">상태</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">마지막 수집</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">작업</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filteredConfigs.map((config) => (
|
||||
<TableRow key={config.id}>
|
||||
<TableCell>
|
||||
<TableRow key={config.id} className="bg-background transition-colors hover:bg-muted/50">
|
||||
<TableCell className="h-16 px-6 py-3 text-sm">
|
||||
<div>
|
||||
<div className="font-medium">{config.config_name}</div>
|
||||
{config.description && (
|
||||
|
|
@ -272,27 +272,27 @@ export default function CollectionManagementPage() {
|
|||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<TableCell className="h-16 px-6 py-3 text-sm">
|
||||
{getTypeBadge(config.collection_type)}
|
||||
</TableCell>
|
||||
<TableCell className="font-mono text-sm">
|
||||
<TableCell className="h-16 px-6 py-3 font-mono text-sm">
|
||||
{config.source_table}
|
||||
</TableCell>
|
||||
<TableCell className="font-mono text-sm">
|
||||
<TableCell className="h-16 px-6 py-3 font-mono text-sm">
|
||||
{config.target_table || "-"}
|
||||
</TableCell>
|
||||
<TableCell className="font-mono text-sm">
|
||||
<TableCell className="h-16 px-6 py-3 font-mono text-sm">
|
||||
{config.schedule_cron || "-"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<TableCell className="h-16 px-6 py-3 text-sm">
|
||||
{getStatusBadge(config.is_active)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<TableCell className="h-16 px-6 py-3 text-sm">
|
||||
{config.last_collected_at
|
||||
? new Date(config.last_collected_at).toLocaleString()
|
||||
: "-"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<TableCell className="h-16 px-6 py-3 text-sm">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" className="h-8 w-8 p-0">
|
||||
|
|
|
|||
|
|
@ -315,43 +315,43 @@ export default function ExternalConnectionsPage() {
|
|||
<div className="rounded-lg border bg-card shadow-sm">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="border-b bg-muted/50 hover:bg-muted/50">
|
||||
<TableHead className="h-12 text-sm font-semibold">연결명</TableHead>
|
||||
<TableHead className="h-12 text-sm font-semibold">DB 타입</TableHead>
|
||||
<TableHead className="h-12 text-sm font-semibold">호스트:포트</TableHead>
|
||||
<TableHead className="h-12 text-sm font-semibold">데이터베이스</TableHead>
|
||||
<TableHead className="h-12 text-sm font-semibold">사용자</TableHead>
|
||||
<TableHead className="h-12 text-sm font-semibold">상태</TableHead>
|
||||
<TableHead className="h-12 text-sm font-semibold">생성일</TableHead>
|
||||
<TableHead className="h-12 text-sm font-semibold">연결 테스트</TableHead>
|
||||
<TableHead className="h-12 text-right text-sm font-semibold">작업</TableHead>
|
||||
<TableRow>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">연결명</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">DB 타입</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">호스트:포트</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">데이터베이스</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">사용자</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">상태</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">생성일</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">연결 테스트</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-right text-sm font-semibold">작업</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{connections.map((connection) => (
|
||||
<TableRow key={connection.id} className="border-b transition-colors hover:bg-muted/50">
|
||||
<TableCell className="h-16 text-sm">
|
||||
<TableRow key={connection.id} className="bg-background transition-colors hover:bg-muted/50">
|
||||
<TableCell className="h-16 px-6 py-3 text-sm">
|
||||
<div className="font-medium">{connection.connection_name}</div>
|
||||
</TableCell>
|
||||
<TableCell className="h-16 text-sm">
|
||||
<TableCell className="h-16 px-6 py-3 text-sm">
|
||||
<Badge variant="outline">
|
||||
{DB_TYPE_LABELS[connection.db_type] || connection.db_type}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="h-16 font-mono text-sm">
|
||||
<TableCell className="h-16 px-6 py-3 font-mono text-sm">
|
||||
{connection.host}:{connection.port}
|
||||
</TableCell>
|
||||
<TableCell className="h-16 font-mono text-sm">{connection.database_name}</TableCell>
|
||||
<TableCell className="h-16 font-mono text-sm">{connection.username}</TableCell>
|
||||
<TableCell className="h-16 text-sm">
|
||||
<TableCell className="h-16 px-6 py-3 font-mono text-sm">{connection.database_name}</TableCell>
|
||||
<TableCell className="h-16 px-6 py-3 font-mono text-sm">{connection.username}</TableCell>
|
||||
<TableCell className="h-16 px-6 py-3 text-sm">
|
||||
<Badge variant={connection.is_active === "Y" ? "default" : "secondary"}>
|
||||
{connection.is_active === "Y" ? "활성" : "비활성"}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="h-16 text-sm">
|
||||
<TableCell className="h-16 px-6 py-3 text-sm">
|
||||
{connection.created_date ? new Date(connection.created_date).toLocaleDateString() : "N/A"}
|
||||
</TableCell>
|
||||
<TableCell className="h-16 text-sm">
|
||||
<TableCell className="h-16 px-6 py-3 text-sm">
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
|
|
@ -369,7 +369,7 @@ export default function ExternalConnectionsPage() {
|
|||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="h-16 text-right">
|
||||
<TableCell className="h-16 px-6 py-3 text-right">
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
|
|
|||
|
|
@ -279,38 +279,38 @@ export default function WebTypesManagePage() {
|
|||
</TableRow>
|
||||
) : (
|
||||
filteredAndSortedWebTypes.map((webType) => (
|
||||
<TableRow key={webType.web_type}>
|
||||
<TableCell className="font-mono">{webType.sort_order || 0}</TableCell>
|
||||
<TableCell className="font-mono">{webType.web_type}</TableCell>
|
||||
<TableCell className="font-medium">
|
||||
<TableRow key={webType.web_type} className="bg-background transition-colors hover:bg-muted/50">
|
||||
<TableCell className="h-16 px-6 py-3 font-mono text-sm">{webType.sort_order || 0}</TableCell>
|
||||
<TableCell className="h-16 px-6 py-3 font-mono text-sm">{webType.web_type}</TableCell>
|
||||
<TableCell className="h-16 px-6 py-3 font-medium text-sm">
|
||||
{webType.type_name}
|
||||
{webType.type_name_eng && (
|
||||
<div className="text-muted-foreground text-xs">{webType.type_name_eng}</div>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<TableCell className="h-16 px-6 py-3 text-sm">
|
||||
<Badge variant="secondary">{webType.category}</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-xs truncate">{webType.description || "-"}</TableCell>
|
||||
<TableCell>
|
||||
<TableCell className="h-16 px-6 py-3 max-w-xs truncate text-sm">{webType.description || "-"}</TableCell>
|
||||
<TableCell className="h-16 px-6 py-3 text-sm">
|
||||
<Badge variant="outline" className="font-mono text-xs">
|
||||
{webType.component_name || "TextWidget"}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<TableCell className="h-16 px-6 py-3 text-sm">
|
||||
<Badge variant="secondary" className="font-mono text-xs">
|
||||
{webType.config_panel === "none" || !webType.config_panel ? "기본 설정" : webType.config_panel}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<TableCell className="h-16 px-6 py-3 text-sm">
|
||||
<Badge variant={webType.is_active === "Y" ? "default" : "secondary"}>
|
||||
{webType.is_active === "Y" ? "활성화" : "비활성화"}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="text-muted-foreground text-sm">
|
||||
<TableCell className="h-16 px-6 py-3 text-muted-foreground text-sm">
|
||||
{webType.updated_date ? new Date(webType.updated_date).toLocaleDateString("ko-KR") : "-"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<TableCell className="h-16 px-6 py-3 text-sm">
|
||||
<div className="flex items-center gap-2">
|
||||
<Link href={`/admin/standards/${webType.web_type}`}>
|
||||
<Button variant="ghost" size="sm">
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { useState, useEffect, useMemo, useCallback } from "react";
|
|||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Search, Database, RefreshCw, Settings, Plus, Activity, Trash2 } from "lucide-react";
|
||||
import { LoadingSpinner } from "@/components/common/LoadingSpinner";
|
||||
|
|
@ -92,6 +93,9 @@ export default function TableManagementPage() {
|
|||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||
const [tableToDelete, setTableToDelete] = useState<string>("");
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
|
||||
// 선택된 테이블 목록 (체크박스)
|
||||
const [selectedTableIds, setSelectedTableIds] = useState<Set<string>>(new Set());
|
||||
|
||||
// 최고 관리자 여부 확인 (회사코드가 "*"인 경우)
|
||||
const isSuperAdmin = user?.companyCode === "*";
|
||||
|
|
@ -594,11 +598,91 @@ export default function TableManagementPage() {
|
|||
}
|
||||
};
|
||||
|
||||
// 체크박스 선택 핸들러
|
||||
const handleTableCheck = (tableName: string, checked: boolean) => {
|
||||
setSelectedTableIds((prev) => {
|
||||
const newSet = new Set(prev);
|
||||
if (checked) {
|
||||
newSet.add(tableName);
|
||||
} else {
|
||||
newSet.delete(tableName);
|
||||
}
|
||||
return newSet;
|
||||
});
|
||||
};
|
||||
|
||||
// 전체 선택/해제
|
||||
const handleSelectAll = (checked: boolean) => {
|
||||
if (checked) {
|
||||
const filteredTables = tables.filter(
|
||||
(table) =>
|
||||
table.tableName.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
(table.displayName && table.displayName.toLowerCase().includes(searchTerm.toLowerCase())),
|
||||
);
|
||||
setSelectedTableIds(new Set(filteredTables.map((table) => table.tableName)));
|
||||
} else {
|
||||
setSelectedTableIds(new Set());
|
||||
}
|
||||
};
|
||||
|
||||
// 일괄 삭제 확인
|
||||
const handleBulkDeleteClick = () => {
|
||||
if (selectedTableIds.size === 0) return;
|
||||
setDeleteDialogOpen(true);
|
||||
};
|
||||
|
||||
// 일괄 삭제 실행
|
||||
const handleBulkDelete = async () => {
|
||||
if (selectedTableIds.size === 0) return;
|
||||
|
||||
setIsDeleting(true);
|
||||
try {
|
||||
const tablesToDelete = Array.from(selectedTableIds);
|
||||
let successCount = 0;
|
||||
let failCount = 0;
|
||||
|
||||
for (const tableName of tablesToDelete) {
|
||||
try {
|
||||
const result = await ddlApi.dropTable(tableName);
|
||||
if (result.success) {
|
||||
successCount++;
|
||||
// 삭제된 테이블이 선택된 테이블이었다면 선택 해제
|
||||
if (selectedTable === tableName) {
|
||||
setSelectedTable(null);
|
||||
setColumns([]);
|
||||
}
|
||||
} else {
|
||||
failCount++;
|
||||
}
|
||||
} catch (error) {
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (successCount > 0) {
|
||||
toast.success(`${successCount}개의 테이블이 성공적으로 삭제되었습니다.`);
|
||||
}
|
||||
if (failCount > 0) {
|
||||
toast.error(`${failCount}개의 테이블 삭제에 실패했습니다.`);
|
||||
}
|
||||
|
||||
// 선택 초기화 및 테이블 목록 새로고침
|
||||
setSelectedTableIds(new Set());
|
||||
await loadTables();
|
||||
} catch (error: any) {
|
||||
toast.error("테이블 삭제 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setIsDeleting(false);
|
||||
setDeleteDialogOpen(false);
|
||||
setTableToDelete("");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-background flex min-h-screen flex-col">
|
||||
<div className="space-y-6 p-6">
|
||||
<div className="bg-background flex h-screen flex-col">
|
||||
<div className="flex h-full flex-col space-y-6 overflow-hidden p-6">
|
||||
{/* 페이지 헤더 */}
|
||||
<div className="space-y-2 border-b pb-4">
|
||||
<div className="flex-shrink-0 space-y-2 border-b pb-4">
|
||||
<div className="flex flex-col gap-2 lg:flex-row lg:items-start lg:justify-between">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">
|
||||
|
|
@ -664,28 +748,65 @@ export default function TableManagementPage() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex h-full gap-6">
|
||||
<div className="flex h-full flex-1 gap-6 overflow-hidden">
|
||||
{/* 좌측 사이드바: 테이블 목록 (20%) */}
|
||||
<div className="w-[20%] border-r pr-6">
|
||||
<div className="space-y-4">
|
||||
<h2 className="flex items-center gap-2 text-lg font-semibold">
|
||||
<Database className="text-muted-foreground h-5 w-5" />
|
||||
{getTextFromUI(TABLE_MANAGEMENT_KEYS.TABLE_NAME, "테이블 목록")}
|
||||
</h2>
|
||||
|
||||
<div className="flex h-full w-[20%] flex-col border-r pr-4">
|
||||
<div className="flex h-full flex-col space-y-4">
|
||||
{/* 검색 */}
|
||||
<div className="relative">
|
||||
<Search className="text-muted-foreground absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2" />
|
||||
<Input
|
||||
placeholder={getTextFromUI(TABLE_MANAGEMENT_KEYS.SEARCH_PLACEHOLDER, "테이블 검색...")}
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="h-10 pl-10 text-sm"
|
||||
/>
|
||||
<div className="flex-shrink-0">
|
||||
<div className="relative">
|
||||
<Search className="text-muted-foreground absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2" />
|
||||
<Input
|
||||
placeholder={getTextFromUI(TABLE_MANAGEMENT_KEYS.SEARCH_PLACEHOLDER, "테이블 검색...")}
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="h-10 pl-10 text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 테이블 목록 */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex-1 space-y-3 overflow-y-auto">
|
||||
{/* 전체 선택 및 일괄 삭제 (최고 관리자만) */}
|
||||
{isSuperAdmin && (
|
||||
<div className="flex items-center justify-between border-b pb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={
|
||||
tables.filter(
|
||||
(table) =>
|
||||
table.tableName.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
(table.displayName && table.displayName.toLowerCase().includes(searchTerm.toLowerCase())),
|
||||
).length > 0 &&
|
||||
tables
|
||||
.filter(
|
||||
(table) =>
|
||||
table.tableName.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
(table.displayName && table.displayName.toLowerCase().includes(searchTerm.toLowerCase())),
|
||||
)
|
||||
.every((table) => selectedTableIds.has(table.tableName))
|
||||
}
|
||||
onCheckedChange={handleSelectAll}
|
||||
aria-label="전체 선택"
|
||||
/>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{selectedTableIds.size > 0 && `${selectedTableIds.size}개 선택됨`}
|
||||
</span>
|
||||
</div>
|
||||
{selectedTableIds.size > 0 && (
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
onClick={handleBulkDeleteClick}
|
||||
className="h-8 gap-2 text-xs"
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
삭제
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<LoadingSpinner />
|
||||
|
|
@ -707,40 +828,42 @@ export default function TableManagementPage() {
|
|||
.map((table) => (
|
||||
<div
|
||||
key={table.tableName}
|
||||
className={`bg-card rounded-lg border p-4 shadow-sm transition-all ${
|
||||
className={`bg-card rounded-lg p-4 shadow-sm transition-all ${
|
||||
selectedTable === table.tableName ? "shadow-md" : "hover:shadow-md"
|
||||
}`}
|
||||
style={
|
||||
selectedTable === table.tableName
|
||||
? { border: "2px solid #000000" }
|
||||
: { border: "2px solid transparent" }
|
||||
}
|
||||
>
|
||||
<div className="cursor-pointer" onClick={() => handleTableSelect(table.tableName)}>
|
||||
<h4 className="text-sm font-semibold">{table.displayName || table.tableName}</h4>
|
||||
<p className="text-muted-foreground mt-1 text-xs">
|
||||
{table.description || getTextFromUI(TABLE_MANAGEMENT_KEYS.TABLE_DESCRIPTION, "설명 없음")}
|
||||
</p>
|
||||
<div className="mt-2 flex items-center justify-between border-t pt-2">
|
||||
<span className="text-muted-foreground text-xs">컬럼</span>
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
{table.columnCount}
|
||||
</Badge>
|
||||
<div className="flex items-start gap-3">
|
||||
{/* 체크박스 (최고 관리자만) */}
|
||||
{isSuperAdmin && (
|
||||
<Checkbox
|
||||
checked={selectedTableIds.has(table.tableName)}
|
||||
onCheckedChange={(checked) => handleTableCheck(table.tableName, checked as boolean)}
|
||||
aria-label={`${table.displayName || table.tableName} 선택`}
|
||||
className="mt-0.5"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className="flex-1 cursor-pointer"
|
||||
onClick={() => handleTableSelect(table.tableName)}
|
||||
>
|
||||
<h4 className="text-sm font-semibold">{table.displayName || table.tableName}</h4>
|
||||
<p className="text-muted-foreground mt-1 text-xs">
|
||||
{table.description || getTextFromUI(TABLE_MANAGEMENT_KEYS.TABLE_DESCRIPTION, "설명 없음")}
|
||||
</p>
|
||||
<div className="mt-2 flex items-center justify-between border-t pt-2">
|
||||
<span className="text-muted-foreground text-xs">컬럼</span>
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
{table.columnCount}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 삭제 버튼 (최고 관리자만) */}
|
||||
{isSuperAdmin && (
|
||||
<div className="mt-2 border-t pt-2">
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
className="h-8 w-full gap-2 text-xs"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleDeleteTableClick(table.tableName);
|
||||
}}
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
테이블 삭제
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
|
|
@ -749,14 +872,9 @@ export default function TableManagementPage() {
|
|||
</div>
|
||||
|
||||
{/* 우측 메인 영역: 컬럼 타입 관리 (80%) */}
|
||||
<div className="w-[80%] pl-0">
|
||||
<div className="flex h-full flex-col space-y-4">
|
||||
<h2 className="flex items-center gap-2 text-xl font-semibold">
|
||||
<Settings className="text-muted-foreground h-5 w-5" />
|
||||
{selectedTable ? <>테이블 설정 - {selectedTable}</> : "테이블 타입 관리"}
|
||||
</h2>
|
||||
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<div className="flex h-full w-[80%] flex-col overflow-hidden pl-0">
|
||||
<div className="flex h-full flex-col space-y-4 overflow-hidden">
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
{!selectedTable ? (
|
||||
<div className="bg-card flex h-64 flex-col items-center justify-center rounded-lg border shadow-sm">
|
||||
<div className="flex flex-col items-center gap-2 text-center">
|
||||
|
|
@ -801,19 +919,19 @@ export default function TableManagementPage() {
|
|||
) : (
|
||||
<div className="space-y-4">
|
||||
{/* 컬럼 헤더 */}
|
||||
<div className="text-foreground flex items-center border-b pb-2 text-sm font-semibold">
|
||||
<div className="w-40 px-4">컬럼명</div>
|
||||
<div className="text-foreground flex h-12 items-center border-b px-6 py-3 text-sm font-semibold">
|
||||
<div className="w-40 pr-4">컬럼명</div>
|
||||
<div className="w-48 px-4">라벨</div>
|
||||
<div className="w-48 px-4">입력 타입</div>
|
||||
<div className="flex-1 px-4" style={{ maxWidth: "calc(100% - 808px)" }}>
|
||||
<div className="w-48 pr-6">입력 타입</div>
|
||||
<div className="flex-1 pl-6" style={{ maxWidth: "calc(100% - 808px)" }}>
|
||||
상세 설정
|
||||
</div>
|
||||
<div className="w-80 px-4">설명</div>
|
||||
<div className="w-80 pl-4">설명</div>
|
||||
</div>
|
||||
|
||||
{/* 컬럼 리스트 */}
|
||||
<div
|
||||
className="max-h-96 overflow-y-auto rounded-lg border"
|
||||
className="max-h-96 overflow-y-auto"
|
||||
onScroll={(e) => {
|
||||
const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
|
||||
// 스크롤이 끝에 가까워지면 더 많은 데이터 로드
|
||||
|
|
@ -825,9 +943,9 @@ export default function TableManagementPage() {
|
|||
{columns.map((column, index) => (
|
||||
<div
|
||||
key={column.columnName}
|
||||
className="hover:bg-muted/50 flex items-center border-b py-2 transition-colors"
|
||||
className="bg-background hover:bg-muted/50 flex min-h-16 items-center border-b px-6 py-3 transition-colors"
|
||||
>
|
||||
<div className="w-40 px-4">
|
||||
<div className="w-40 pr-4">
|
||||
<div className="font-mono text-sm">{column.columnName}</div>
|
||||
</div>
|
||||
<div className="w-48 px-4">
|
||||
|
|
@ -838,7 +956,7 @@ export default function TableManagementPage() {
|
|||
className="h-8 text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-48 px-4">
|
||||
<div className="w-48 pr-6">
|
||||
<Select
|
||||
value={column.inputType || "text"}
|
||||
onValueChange={(value) => handleInputTypeChange(column.columnName, value)}
|
||||
|
|
@ -855,7 +973,7 @@ export default function TableManagementPage() {
|
|||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex-1 px-4" style={{ maxWidth: "calc(100% - 808px)" }}>
|
||||
<div className="flex-1 pl-6" style={{ maxWidth: "calc(100% - 808px)" }}>
|
||||
{/* 웹 타입이 'code'인 경우 공통코드 선택 */}
|
||||
{column.inputType === "code" && (
|
||||
<Select
|
||||
|
|
@ -878,58 +996,98 @@ export default function TableManagementPage() {
|
|||
)}
|
||||
{/* 웹 타입이 'entity'인 경우 참조 테이블 선택 */}
|
||||
{column.inputType === "entity" && (
|
||||
<div className="space-y-1">
|
||||
{/* Entity 타입 설정 - 가로 배치 */}
|
||||
<div className="border-primary/20 bg-primary/5 rounded-lg border p-2">
|
||||
<div className="mb-2 flex items-center gap-2">
|
||||
<span className="text-primary text-xs font-medium">Entity 설정</span>
|
||||
<div className="space-y-2">
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{/* 참조 테이블 */}
|
||||
<div>
|
||||
<label className="text-muted-foreground mb-1 block text-xs">
|
||||
참조 테이블
|
||||
</label>
|
||||
<Select
|
||||
value={column.referenceTable || "none"}
|
||||
onValueChange={(value) =>
|
||||
handleDetailSettingsChange(column.columnName, "entity", value)
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="bg-background h-8 text-xs">
|
||||
<SelectValue placeholder="선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{referenceTableOptions.map((option, index) => (
|
||||
<SelectItem
|
||||
key={`entity-${option.value}-${index}`}
|
||||
value={option.value}
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">{option.label}</span>
|
||||
<span className="text-muted-foreground text-xs">
|
||||
{option.value}
|
||||
</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{/* 참조 테이블 */}
|
||||
{/* 조인 컬럼 */}
|
||||
{column.referenceTable && column.referenceTable !== "none" && (
|
||||
<div>
|
||||
<label className="text-muted-foreground mb-1 block text-xs">
|
||||
참조 테이블
|
||||
조인 컬럼
|
||||
</label>
|
||||
<Select
|
||||
value={column.referenceTable || "none"}
|
||||
value={column.referenceColumn || "none"}
|
||||
onValueChange={(value) =>
|
||||
handleDetailSettingsChange(column.columnName, "entity", value)
|
||||
handleDetailSettingsChange(
|
||||
column.columnName,
|
||||
"entity_reference_column",
|
||||
value,
|
||||
)
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="bg-background h-8 text-xs">
|
||||
<SelectValue placeholder="선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{referenceTableOptions.map((option, index) => (
|
||||
<SelectItem value="none">-- 선택 안함 --</SelectItem>
|
||||
{referenceTableColumns[column.referenceTable]?.map((refCol, index) => (
|
||||
<SelectItem
|
||||
key={`entity-${option.value}-${index}`}
|
||||
value={option.value}
|
||||
key={`ref-col-${refCol.columnName}-${index}`}
|
||||
value={refCol.columnName}
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">{option.label}</span>
|
||||
<span className="text-muted-foreground text-xs">
|
||||
{option.value}
|
||||
</span>
|
||||
</div>
|
||||
<span className="font-medium">{refCol.columnName}</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
{(!referenceTableColumns[column.referenceTable] ||
|
||||
referenceTableColumns[column.referenceTable].length === 0) && (
|
||||
<SelectItem value="loading" disabled>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="border-primary h-3 w-3 animate-spin rounded-full border border-t-transparent"></div>
|
||||
로딩중
|
||||
</div>
|
||||
</SelectItem>
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 조인 컬럼 */}
|
||||
{column.referenceTable && column.referenceTable !== "none" && (
|
||||
{/* 표시 컬럼 */}
|
||||
{column.referenceTable &&
|
||||
column.referenceTable !== "none" &&
|
||||
column.referenceColumn &&
|
||||
column.referenceColumn !== "none" && (
|
||||
<div>
|
||||
<label className="text-muted-foreground mb-1 block text-xs">
|
||||
조인 컬럼
|
||||
표시 컬럼
|
||||
</label>
|
||||
<Select
|
||||
value={column.referenceColumn || "none"}
|
||||
value={column.displayColumn || "none"}
|
||||
onValueChange={(value) =>
|
||||
handleDetailSettingsChange(
|
||||
column.columnName,
|
||||
"entity_reference_column",
|
||||
"entity_display_column",
|
||||
value,
|
||||
)
|
||||
}
|
||||
|
|
@ -960,31 +1118,32 @@ export default function TableManagementPage() {
|
|||
</Select>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 설정 완료 표시 - 간소화 */}
|
||||
{column.referenceTable &&
|
||||
column.referenceTable !== "none" &&
|
||||
column.referenceColumn &&
|
||||
column.referenceColumn !== "none" &&
|
||||
column.displayColumn &&
|
||||
column.displayColumn !== "none" && (
|
||||
<div className="bg-primary/10 text-primary mt-1 flex items-center gap-1 rounded px-2 py-1 text-xs">
|
||||
<span>✓</span>
|
||||
<span className="truncate">
|
||||
{column.columnName} → {column.referenceTable}.{column.displayColumn}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 설정 완료 표시 */}
|
||||
{column.referenceTable &&
|
||||
column.referenceTable !== "none" &&
|
||||
column.referenceColumn &&
|
||||
column.referenceColumn !== "none" &&
|
||||
column.displayColumn &&
|
||||
column.displayColumn !== "none" && (
|
||||
<div className="bg-primary/10 text-primary mt-2 flex items-center gap-1 rounded px-2 py-1 text-xs">
|
||||
<span>✓</span>
|
||||
<span className="truncate">
|
||||
{column.columnName} → {column.referenceTable}.{column.displayColumn}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{/* 다른 웹 타입인 경우 빈 공간 */}
|
||||
{column.inputType !== "code" && column.inputType !== "entity" && (
|
||||
<div className="text-muted-foreground flex h-8 items-center text-xs">-</div>
|
||||
<div className="text-muted-foreground flex h-8 items-center justify-center text-xs">
|
||||
-
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="w-80 px-4">
|
||||
<div className="w-80 pl-4">
|
||||
<Input
|
||||
value={column.description || ""}
|
||||
onChange={(e) => handleColumnChange(index, "description", e.target.value)}
|
||||
|
|
@ -1075,26 +1234,62 @@ export default function TableManagementPage() {
|
|||
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
||||
<DialogContent className="max-w-[95vw] sm:max-w-[500px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-base sm:text-lg">테이블 삭제 확인</DialogTitle>
|
||||
<DialogTitle className="text-base sm:text-lg">
|
||||
{selectedTableIds.size > 0 ? "테이블 일괄 삭제 확인" : "테이블 삭제 확인"}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-xs sm:text-sm">
|
||||
정말로 테이블을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.
|
||||
{selectedTableIds.size > 0 ? (
|
||||
<>
|
||||
선택된 <strong>{selectedTableIds.size}개</strong>의 테이블을 삭제하시겠습니까?
|
||||
<br />
|
||||
이 작업은 되돌릴 수 없습니다.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
정말로 테이블을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.
|
||||
</>
|
||||
)}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-3 sm:space-y-4">
|
||||
<div className="border-destructive/50 bg-destructive/10 rounded-lg border p-4">
|
||||
<p className="text-destructive text-sm font-semibold">경고</p>
|
||||
<p className="text-destructive/80 mt-1.5 text-sm">
|
||||
테이블 <span className="font-mono font-bold">{tableToDelete}</span>과 모든 데이터가 영구적으로
|
||||
삭제됩니다.
|
||||
</p>
|
||||
{selectedTableIds.size === 0 && tableToDelete && (
|
||||
<div className="space-y-3 sm:space-y-4">
|
||||
<div className="border-destructive/50 bg-destructive/10 rounded-lg border p-4">
|
||||
<p className="text-destructive text-sm font-semibold">경고</p>
|
||||
<p className="text-destructive/80 mt-1.5 text-sm">
|
||||
테이블 <span className="font-mono font-bold">{tableToDelete}</span>과 모든 데이터가 영구적으로
|
||||
삭제됩니다.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedTableIds.size > 0 && (
|
||||
<div className="space-y-3 sm:space-y-4">
|
||||
<div className="border-destructive/50 bg-destructive/10 rounded-lg border p-4">
|
||||
<p className="text-destructive text-sm font-semibold">경고</p>
|
||||
<p className="text-destructive/80 mt-1.5 text-sm">
|
||||
다음 테이블들과 모든 데이터가 영구적으로 삭제됩니다:
|
||||
</p>
|
||||
<ul className="text-destructive/80 mt-2 list-disc pl-5 text-sm">
|
||||
{Array.from(selectedTableIds).map((tableName) => (
|
||||
<li key={tableName} className="font-mono">
|
||||
{tableName}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<DialogFooter className="gap-2 sm:gap-0">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setDeleteDialogOpen(false)}
|
||||
onClick={() => {
|
||||
setDeleteDialogOpen(false);
|
||||
setTableToDelete("");
|
||||
setSelectedTableIds(new Set());
|
||||
}}
|
||||
disabled={isDeleting}
|
||||
className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
|
||||
>
|
||||
|
|
@ -1102,7 +1297,7 @@ export default function TableManagementPage() {
|
|||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={handleDeleteTable}
|
||||
onClick={selectedTableIds.size > 0 ? handleBulkDelete : handleDeleteTable}
|
||||
disabled={isDeleting}
|
||||
className="h-8 flex-1 gap-2 text-xs sm:h-10 sm:flex-none sm:text-sm"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -240,7 +240,7 @@ export default function TemplatesManagePage() {
|
|||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[60px]">
|
||||
<TableHead className="h-12 px-6 py-3 w-[60px]">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
|
|
@ -251,7 +251,7 @@ export default function TemplatesManagePage() {
|
|||
<ArrowUpDown className="ml-1 h-3 w-3" />
|
||||
</Button>
|
||||
</TableHead>
|
||||
<TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
|
|
@ -262,7 +262,7 @@ export default function TemplatesManagePage() {
|
|||
<ArrowUpDown className="ml-1 h-3 w-3" />
|
||||
</Button>
|
||||
</TableHead>
|
||||
<TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
|
|
@ -273,14 +273,14 @@ export default function TemplatesManagePage() {
|
|||
<ArrowUpDown className="ml-1 h-3 w-3" />
|
||||
</Button>
|
||||
</TableHead>
|
||||
<TableHead>카테고리</TableHead>
|
||||
<TableHead>설명</TableHead>
|
||||
<TableHead>아이콘</TableHead>
|
||||
<TableHead>기본 크기</TableHead>
|
||||
<TableHead>공개 여부</TableHead>
|
||||
<TableHead>활성화</TableHead>
|
||||
<TableHead>수정일</TableHead>
|
||||
<TableHead className="w-[200px]">작업</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">카테고리</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">설명</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">아이콘</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">기본 크기</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">공개 여부</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">활성화</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">수정일</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 w-[200px] text-sm font-semibold">작업</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
|
|
@ -299,39 +299,39 @@ export default function TemplatesManagePage() {
|
|||
</TableRow>
|
||||
) : (
|
||||
filteredAndSortedTemplates.map((template) => (
|
||||
<TableRow key={template.template_code}>
|
||||
<TableCell className="font-mono">{template.sort_order || 0}</TableCell>
|
||||
<TableCell className="font-mono">{template.template_code}</TableCell>
|
||||
<TableCell className="font-medium">
|
||||
<TableRow key={template.template_code} className="bg-background transition-colors hover:bg-muted/50">
|
||||
<TableCell className="h-16 px-6 py-3 font-mono text-sm">{template.sort_order || 0}</TableCell>
|
||||
<TableCell className="h-16 px-6 py-3 font-mono text-sm">{template.template_code}</TableCell>
|
||||
<TableCell className="h-16 px-6 py-3 font-medium text-sm">
|
||||
{template.template_name}
|
||||
{template.template_name_eng && (
|
||||
<div className="text-muted-foreground text-xs">{template.template_name_eng}</div>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<TableCell className="h-16 px-6 py-3 text-sm">
|
||||
<Badge variant="secondary">{template.category}</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-xs truncate">{template.description || "-"}</TableCell>
|
||||
<TableCell>
|
||||
<TableCell className="h-16 px-6 py-3 max-w-xs truncate text-sm">{template.description || "-"}</TableCell>
|
||||
<TableCell className="h-16 px-6 py-3 text-sm">
|
||||
<div className="flex items-center justify-center">{renderIcon(template.icon_name)}</div>
|
||||
</TableCell>
|
||||
<TableCell className="font-mono text-xs">
|
||||
<TableCell className="h-16 px-6 py-3 font-mono text-xs">
|
||||
{template.default_size ? `${template.default_size.width}×${template.default_size.height}` : "-"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<TableCell className="h-16 px-6 py-3 text-sm">
|
||||
<Badge variant={template.is_public === "Y" ? "default" : "secondary"}>
|
||||
{template.is_public === "Y" ? "공개" : "비공개"}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<TableCell className="h-16 px-6 py-3 text-sm">
|
||||
<Badge variant={template.is_active === "Y" ? "default" : "secondary"}>
|
||||
{template.is_active === "Y" ? "활성화" : "비활성화"}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="text-muted-foreground text-sm">
|
||||
<TableCell className="h-16 px-6 py-3 text-muted-foreground text-sm">
|
||||
{template.updated_date ? new Date(template.updated_date).toLocaleDateString("ko-KR") : "-"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<TableCell className="h-16 px-6 py-3 text-sm">
|
||||
<div className="flex space-x-1">
|
||||
<Button asChild size="sm" variant="ghost">
|
||||
<Link href={`/admin/templates/${template.template_code}`}>
|
||||
|
|
|
|||
|
|
@ -54,12 +54,12 @@ export function CompanyTable({ companies, isLoading, onEdit, onDelete }: Company
|
|||
<TableHeader>
|
||||
<TableRow>
|
||||
{COMPANY_TABLE_COLUMNS.map((column) => (
|
||||
<TableHead key={column.key} className="h-12 text-sm font-semibold">
|
||||
<TableHead key={column.key} className="h-12 px-6 py-3 text-sm font-semibold">
|
||||
{column.label}
|
||||
</TableHead>
|
||||
))}
|
||||
<TableHead className="h-12 text-sm font-semibold">디스크 사용량</TableHead>
|
||||
<TableHead className="h-12 text-sm font-semibold">작업</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">디스크 사용량</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">작업</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
|
|
@ -134,22 +134,22 @@ export function CompanyTable({ companies, isLoading, onEdit, onDelete }: Company
|
|||
<TableHeader>
|
||||
<TableRow>
|
||||
{COMPANY_TABLE_COLUMNS.map((column) => (
|
||||
<TableHead key={column.key} className="h-12 text-sm font-semibold">
|
||||
<TableHead key={column.key} className="h-12 px-6 py-3 text-sm font-semibold">
|
||||
{column.label}
|
||||
</TableHead>
|
||||
))}
|
||||
<TableHead className="h-12 text-sm font-semibold">디스크 사용량</TableHead>
|
||||
<TableHead className="h-12 text-sm font-semibold">작업</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">디스크 사용량</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">작업</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{companies.map((company) => (
|
||||
<TableRow key={company.regdate + company.company_code} className="transition-colors hover:bg-muted/50">
|
||||
<TableCell className="h-16 font-mono text-sm">{company.company_code}</TableCell>
|
||||
<TableCell className="h-16 text-sm font-medium">{company.company_name}</TableCell>
|
||||
<TableCell className="h-16 text-sm">{company.writer}</TableCell>
|
||||
<TableCell className="h-16">{formatDiskUsage(company)}</TableCell>
|
||||
<TableCell className="h-16">
|
||||
<TableRow key={company.regdate + company.company_code} className="bg-background transition-colors hover:bg-muted/50">
|
||||
<TableCell className="h-16 px-6 py-3 font-mono text-sm">{company.company_code}</TableCell>
|
||||
<TableCell className="h-16 px-6 py-3 text-sm font-medium">{company.company_name}</TableCell>
|
||||
<TableCell className="h-16 px-6 py-3 text-sm">{company.writer}</TableCell>
|
||||
<TableCell className="h-16 px-6 py-3">{formatDiskUsage(company)}</TableCell>
|
||||
<TableCell className="h-16 px-6 py-3">
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
|
|
|||
|
|
@ -113,11 +113,11 @@ export function UserTable({
|
|||
<TableHeader>
|
||||
<TableRow>
|
||||
{USER_TABLE_COLUMNS.map((column) => (
|
||||
<TableHead key={column.key} style={{ width: column.width }} className="h-12 text-sm font-semibold">
|
||||
<TableHead key={column.key} style={{ width: column.width }} className="h-12 px-6 py-3 text-sm font-semibold">
|
||||
{column.label}
|
||||
</TableHead>
|
||||
))}
|
||||
<TableHead className="h-12 w-[200px] text-sm font-semibold">작업</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 w-[200px] text-sm font-semibold">작업</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
|
|
@ -191,29 +191,29 @@ export function UserTable({
|
|||
<TableHeader>
|
||||
<TableRow>
|
||||
{USER_TABLE_COLUMNS.map((column) => (
|
||||
<TableHead key={column.key} style={{ width: column.width }} className="h-12 text-sm font-semibold">
|
||||
<TableHead key={column.key} style={{ width: column.width }} className="h-12 px-6 py-3 text-sm font-semibold">
|
||||
{column.label}
|
||||
</TableHead>
|
||||
))}
|
||||
<TableHead className="h-12 w-[200px] text-sm font-semibold">작업</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 w-[200px] text-sm font-semibold">작업</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{users.map((user, index) => (
|
||||
<TableRow key={`${user.userId}-${index}`} className="hover:bg-muted/50 transition-colors">
|
||||
<TableCell className="h-16 font-mono text-sm font-medium">{getRowNumber(index)}</TableCell>
|
||||
<TableCell className="h-16 font-mono text-sm">{user.sabun || "-"}</TableCell>
|
||||
<TableCell className="h-16 text-sm font-medium">{user.companyCode || "-"}</TableCell>
|
||||
<TableCell className="h-16 text-sm font-medium">{user.deptName || "-"}</TableCell>
|
||||
<TableCell className="h-16 text-sm font-medium">{user.positionName || "-"}</TableCell>
|
||||
<TableCell className="h-16 font-mono text-sm">{user.userId}</TableCell>
|
||||
<TableCell className="h-16 text-sm font-medium">{user.userName}</TableCell>
|
||||
<TableCell className="h-16 text-sm">{user.tel || user.cellPhone || "-"}</TableCell>
|
||||
<TableCell className="h-16 max-w-[200px] truncate text-sm" title={user.email}>
|
||||
<TableRow key={`${user.userId}-${index}`} className="bg-background transition-colors hover:bg-muted/50">
|
||||
<TableCell className="h-16 px-6 py-3 font-mono text-sm font-medium">{getRowNumber(index)}</TableCell>
|
||||
<TableCell className="h-16 px-6 py-3 font-mono text-sm">{user.sabun || "-"}</TableCell>
|
||||
<TableCell className="h-16 px-6 py-3 text-sm font-medium">{user.companyCode || "-"}</TableCell>
|
||||
<TableCell className="h-16 px-6 py-3 text-sm font-medium">{user.deptName || "-"}</TableCell>
|
||||
<TableCell className="h-16 px-6 py-3 text-sm font-medium">{user.positionName || "-"}</TableCell>
|
||||
<TableCell className="h-16 px-6 py-3 font-mono text-sm">{user.userId}</TableCell>
|
||||
<TableCell className="h-16 px-6 py-3 text-sm font-medium">{user.userName}</TableCell>
|
||||
<TableCell className="h-16 px-6 py-3 text-sm">{user.tel || user.cellPhone || "-"}</TableCell>
|
||||
<TableCell className="h-16 px-6 py-3 max-w-[200px] truncate text-sm" title={user.email}>
|
||||
{user.email || "-"}
|
||||
</TableCell>
|
||||
<TableCell className="h-16 text-sm">{formatDate(user.regDate || "")}</TableCell>
|
||||
<TableCell className="h-16">
|
||||
<TableCell className="h-16 px-6 py-3 text-sm">{formatDate(user.regDate || "")}</TableCell>
|
||||
<TableCell className="h-16 px-6 py-3">
|
||||
<div className="flex items-center">
|
||||
<Switch
|
||||
checked={user.status === "active"}
|
||||
|
|
@ -222,7 +222,7 @@ export function UserTable({
|
|||
/>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="h-16">
|
||||
<TableCell className="h-16 px-6 py-3">
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
|
|
|||
|
|
@ -429,31 +429,28 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
|
|||
{/* 활성 화면 탭 */}
|
||||
<TabsContent value="active">
|
||||
{/* 데스크톱 테이블 뷰 (lg 이상) */}
|
||||
<div className="bg-card hidden rounded-lg border shadow-sm lg:block">
|
||||
<div className="border-b p-6">
|
||||
<h3 className="text-lg font-semibold">화면 목록 ({screens.length})</h3>
|
||||
</div>
|
||||
<div className="bg-card hidden shadow-sm lg:block">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="bg-muted/50 hover:bg-muted/50 border-b">
|
||||
<TableHead className="h-12 text-sm font-semibold">화면명</TableHead>
|
||||
<TableHead className="h-12 text-sm font-semibold">화면 코드</TableHead>
|
||||
<TableHead className="h-12 text-sm font-semibold">테이블명</TableHead>
|
||||
<TableHead className="h-12 text-sm font-semibold">상태</TableHead>
|
||||
<TableHead className="h-12 text-sm font-semibold">생성일</TableHead>
|
||||
<TableHead className="h-12 text-sm font-semibold">작업</TableHead>
|
||||
<TableRow>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">화면명</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">화면 코드</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">테이블명</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">상태</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">생성일</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">작업</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{screens.map((screen) => (
|
||||
<TableRow
|
||||
key={screen.screenId}
|
||||
className={`hover:bg-muted/50 cursor-pointer border-b transition-colors ${
|
||||
className={`bg-background hover:bg-muted/50 cursor-pointer border-b transition-colors ${
|
||||
selectedScreen?.screenId === screen.screenId ? "border-primary/20 bg-accent" : ""
|
||||
}`}
|
||||
onClick={() => onDesignScreen(screen)}
|
||||
>
|
||||
<TableCell className="h-16 cursor-pointer">
|
||||
<TableCell className="h-16 px-6 py-3 cursor-pointer">
|
||||
<div>
|
||||
<div className="font-medium">{screen.screenName}</div>
|
||||
{screen.description && (
|
||||
|
|
@ -461,26 +458,26 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
|
|||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="h-16">
|
||||
<TableCell className="h-16 px-6 py-3">
|
||||
<Badge variant="outline" className="font-mono">
|
||||
{screen.screenCode}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="h-16">
|
||||
<TableCell className="h-16 px-6 py-3">
|
||||
<span className="text-muted-foreground font-mono text-sm">
|
||||
{screen.tableLabel || screen.tableName}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell className="h-16">
|
||||
<TableCell className="h-16 px-6 py-3">
|
||||
<Badge variant={screen.isActive === "Y" ? "default" : "secondary"}>
|
||||
{screen.isActive === "Y" ? "활성" : "비활성"}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="h-16">
|
||||
<TableCell className="h-16 px-6 py-3">
|
||||
<div className="text-muted-foreground text-sm">{screen.createdDate.toLocaleDateString()}</div>
|
||||
<div className="text-muted-foreground text-xs">{screen.createdBy}</div>
|
||||
</TableCell>
|
||||
<TableCell className="h-16">
|
||||
<TableCell className="h-16 px-6 py-3">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8">
|
||||
|
|
@ -671,52 +668,37 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
|
|||
{/* 휴지통 탭 */}
|
||||
<TabsContent value="trash">
|
||||
{/* 데스크톱 테이블 뷰 (lg 이상) */}
|
||||
<div className="bg-card hidden rounded-lg border shadow-sm lg:block">
|
||||
<div className="flex items-center justify-between border-b p-6">
|
||||
<h3 className="text-lg font-semibold">휴지통 ({deletedScreens.length})</h3>
|
||||
{selectedScreenIds.length > 0 && (
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
onClick={handleBulkDelete}
|
||||
disabled={bulkDeleting}
|
||||
className="h-9 gap-2 text-sm font-medium"
|
||||
>
|
||||
<Trash className="h-4 w-4" />
|
||||
{bulkDeleting ? "삭제 중..." : `선택된 ${selectedScreenIds.length}개 영구삭제`}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className="bg-card hidden shadow-sm lg:block">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="bg-muted/50 hover:bg-muted/50 border-b">
|
||||
<TableHead className="h-12 w-12">
|
||||
<TableRow>
|
||||
<TableHead className="h-12 w-12 px-6 py-3">
|
||||
<Checkbox
|
||||
checked={deletedScreens.length > 0 && selectedScreenIds.length === deletedScreens.length}
|
||||
onCheckedChange={handleSelectAll}
|
||||
aria-label="전체 선택"
|
||||
/>
|
||||
</TableHead>
|
||||
<TableHead className="h-12 text-sm font-semibold">화면명</TableHead>
|
||||
<TableHead className="h-12 text-sm font-semibold">화면 코드</TableHead>
|
||||
<TableHead className="h-12 text-sm font-semibold">테이블명</TableHead>
|
||||
<TableHead className="h-12 text-sm font-semibold">삭제일</TableHead>
|
||||
<TableHead className="h-12 text-sm font-semibold">삭제자</TableHead>
|
||||
<TableHead className="h-12 text-sm font-semibold">삭제 사유</TableHead>
|
||||
<TableHead className="h-12 text-sm font-semibold">작업</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">화면명</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">화면 코드</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">테이블명</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">삭제일</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">삭제자</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">삭제 사유</TableHead>
|
||||
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">작업</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{deletedScreens.map((screen) => (
|
||||
<TableRow key={screen.screenId} className="hover:bg-muted/50 border-b transition-colors">
|
||||
<TableCell className="h-16">
|
||||
<TableRow key={screen.screenId} className="bg-background hover:bg-muted/50 border-b transition-colors">
|
||||
<TableCell className="h-16 px-6 py-3">
|
||||
<Checkbox
|
||||
checked={selectedScreenIds.includes(screen.screenId)}
|
||||
onCheckedChange={(checked) => handleScreenCheck(screen.screenId, checked as boolean)}
|
||||
aria-label={`${screen.screenName} 선택`}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="h-16">
|
||||
<TableCell className="h-16 px-6 py-3">
|
||||
<div>
|
||||
<div className="font-medium">{screen.screenName}</div>
|
||||
{screen.description && (
|
||||
|
|
@ -724,28 +706,28 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
|
|||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="h-16">
|
||||
<TableCell className="h-16 px-6 py-3">
|
||||
<Badge variant="outline" className="font-mono">
|
||||
{screen.screenCode}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="h-16">
|
||||
<TableCell className="h-16 px-6 py-3">
|
||||
<span className="text-muted-foreground font-mono text-sm">
|
||||
{screen.tableLabel || screen.tableName}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell className="h-16">
|
||||
<TableCell className="h-16 px-6 py-3">
|
||||
<div className="text-muted-foreground text-sm">{screen.deletedDate?.toLocaleDateString()}</div>
|
||||
</TableCell>
|
||||
<TableCell className="h-16">
|
||||
<TableCell className="h-16 px-6 py-3">
|
||||
<div className="text-muted-foreground text-sm">{screen.deletedBy}</div>
|
||||
</TableCell>
|
||||
<TableCell className="h-16">
|
||||
<TableCell className="h-16 px-6 py-3">
|
||||
<div className="text-muted-foreground max-w-32 truncate text-sm" title={screen.deleteReason}>
|
||||
{screen.deleteReason || "-"}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="h-16">
|
||||
<TableCell className="h-16 px-6 py-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
|
|
@ -789,7 +771,6 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
|
|||
onCheckedChange={handleSelectAll}
|
||||
aria-label="전체 선택"
|
||||
/>
|
||||
<h3 className="text-base font-semibold">휴지통 ({deletedScreens.length})</h3>
|
||||
</div>
|
||||
{selectedScreenIds.length > 0 && (
|
||||
<Button
|
||||
|
|
|
|||
Loading…
Reference in New Issue