275 lines
9.9 KiB
TypeScript
275 lines
9.9 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuTrigger,
|
|
} from "@/components/ui/dropdown-menu";
|
|
import { MoreHorizontal, Edit, Trash2, Copy, Eye, Plus, Search, Palette } from "lucide-react";
|
|
import { ScreenDefinition } from "@/types/screen";
|
|
import { screenApi } from "@/lib/api/screen";
|
|
import CreateScreenModal from "./CreateScreenModal";
|
|
import CopyScreenModal from "./CopyScreenModal";
|
|
|
|
interface ScreenListProps {
|
|
onScreenSelect: (screen: ScreenDefinition) => void;
|
|
selectedScreen: ScreenDefinition | null;
|
|
onDesignScreen: (screen: ScreenDefinition) => void;
|
|
}
|
|
|
|
export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScreen }: ScreenListProps) {
|
|
const [screens, setScreens] = useState<ScreenDefinition[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [searchTerm, setSearchTerm] = useState("");
|
|
const [currentPage, setCurrentPage] = useState(1);
|
|
const [totalPages, setTotalPages] = useState(1);
|
|
const [isCreateOpen, setIsCreateOpen] = useState(false);
|
|
const [isCopyOpen, setIsCopyOpen] = useState(false);
|
|
const [screenToCopy, setScreenToCopy] = useState<ScreenDefinition | null>(null);
|
|
|
|
// 화면 목록 로드 (실제 API)
|
|
useEffect(() => {
|
|
let abort = false;
|
|
const load = async () => {
|
|
try {
|
|
setLoading(true);
|
|
const resp = await screenApi.getScreens({ page: currentPage, size: 20, searchTerm });
|
|
if (abort) return;
|
|
// 응답 표준: { success, data, total }
|
|
setScreens(resp.data || []);
|
|
setTotalPages(Math.max(1, Math.ceil((resp.total || 0) / 20)));
|
|
} catch (e) {
|
|
console.error("화면 목록 조회 실패", e);
|
|
setScreens([]);
|
|
setTotalPages(1);
|
|
} finally {
|
|
if (!abort) setLoading(false);
|
|
}
|
|
};
|
|
load();
|
|
return () => {
|
|
abort = true;
|
|
};
|
|
}, [currentPage, searchTerm]);
|
|
|
|
const filteredScreens = screens; // 서버 필터 기준 사용
|
|
|
|
// 화면 목록 다시 로드
|
|
const reloadScreens = async () => {
|
|
try {
|
|
setLoading(true);
|
|
const resp = await screenApi.getScreens({ page: currentPage, size: 20, searchTerm });
|
|
setScreens(resp.data || []);
|
|
setTotalPages(Math.max(1, Math.ceil((resp.total || 0) / 20)));
|
|
} catch (e) {
|
|
console.error("화면 목록 조회 실패", e);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleScreenSelect = (screen: ScreenDefinition) => {
|
|
onScreenSelect(screen);
|
|
};
|
|
|
|
const handleEdit = (screen: ScreenDefinition) => {
|
|
// 편집 모달 열기
|
|
console.log("편집:", screen);
|
|
};
|
|
|
|
const handleDelete = (screen: ScreenDefinition) => {
|
|
if (confirm(`"${screen.screenName}" 화면을 삭제하시겠습니까?`)) {
|
|
// 삭제 API 호출
|
|
console.log("삭제:", screen);
|
|
}
|
|
};
|
|
|
|
const handleCopy = (screen: ScreenDefinition) => {
|
|
setScreenToCopy(screen);
|
|
setIsCopyOpen(true);
|
|
};
|
|
|
|
const handleView = (screen: ScreenDefinition) => {
|
|
// 미리보기 모달 열기
|
|
console.log("미리보기:", screen);
|
|
};
|
|
|
|
const handleCopySuccess = () => {
|
|
// 복사 성공 후 화면 목록 다시 로드
|
|
reloadScreens();
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="flex items-center justify-center py-8">
|
|
<div className="text-gray-500">로딩 중...</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
{/* 검색 및 필터 */}
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center space-x-2">
|
|
<div className="relative">
|
|
<Search className="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 transform text-gray-400" />
|
|
<Input
|
|
placeholder="화면명, 코드, 테이블명으로 검색..."
|
|
value={searchTerm}
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
className="w-80 pl-10"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<Button className="bg-blue-600 hover:bg-blue-700" onClick={() => setIsCreateOpen(true)}>
|
|
<Plus className="mr-2 h-4 w-4" />새 화면 생성
|
|
</Button>
|
|
</div>
|
|
|
|
{/* 화면 목록 테이블 */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center justify-between">
|
|
<span>화면 목록 ({screens.length})</span>
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>화면명</TableHead>
|
|
<TableHead>화면 코드</TableHead>
|
|
<TableHead>테이블명</TableHead>
|
|
<TableHead>상태</TableHead>
|
|
<TableHead>생성일</TableHead>
|
|
<TableHead>작업</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{screens.map((screen) => (
|
|
<TableRow
|
|
key={screen.screenId}
|
|
className={`cursor-pointer hover:bg-gray-50 ${
|
|
selectedScreen?.screenId === screen.screenId ? "border-blue-200 bg-blue-50" : ""
|
|
}`}
|
|
onClick={() => handleScreenSelect(screen)}
|
|
>
|
|
<TableCell>
|
|
<div>
|
|
<div className="font-medium text-gray-900">{screen.screenName}</div>
|
|
{screen.description && <div className="mt-1 text-sm text-gray-500">{screen.description}</div>}
|
|
</div>
|
|
</TableCell>
|
|
<TableCell>
|
|
<Badge variant="outline" className="font-mono">
|
|
{screen.screenCode}
|
|
</Badge>
|
|
</TableCell>
|
|
<TableCell>
|
|
<span className="font-mono text-sm text-gray-600">{screen.tableName}</span>
|
|
</TableCell>
|
|
<TableCell>
|
|
<Badge
|
|
variant={screen.isActive === "Y" ? "default" : "secondary"}
|
|
className={screen.isActive === "Y" ? "bg-green-100 text-green-800" : "bg-gray-100 text-gray-800"}
|
|
>
|
|
{screen.isActive === "Y" ? "활성" : "비활성"}
|
|
</Badge>
|
|
</TableCell>
|
|
<TableCell>
|
|
<div className="text-sm text-gray-600">{screen.createdDate.toLocaleDateString()}</div>
|
|
<div className="text-xs text-gray-400">{screen.createdBy}</div>
|
|
</TableCell>
|
|
<TableCell>
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button variant="ghost" className="h-8 w-8 p-0">
|
|
<MoreHorizontal className="h-4 w-4" />
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end">
|
|
<DropdownMenuItem onClick={() => onDesignScreen(screen)}>
|
|
<Palette className="mr-2 h-4 w-4" />
|
|
화면 설계
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem onClick={() => handleView(screen)}>
|
|
<Eye className="mr-2 h-4 w-4" />
|
|
미리보기
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem onClick={() => handleEdit(screen)}>
|
|
<Edit className="mr-2 h-4 w-4" />
|
|
편집
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem onClick={() => handleCopy(screen)}>
|
|
<Copy className="mr-2 h-4 w-4" />
|
|
복사
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem onClick={() => handleDelete(screen)} className="text-red-600">
|
|
<Trash2 className="mr-2 h-4 w-4" />
|
|
삭제
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
|
|
{filteredScreens.length === 0 && <div className="py-8 text-center text-gray-500">검색 결과가 없습니다.</div>}
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* 페이지네이션 */}
|
|
{totalPages > 1 && (
|
|
<div className="flex items-center justify-center space-x-2">
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => setCurrentPage(Math.max(1, currentPage - 1))}
|
|
disabled={currentPage === 1}
|
|
>
|
|
이전
|
|
</Button>
|
|
<span className="text-sm text-gray-600">
|
|
{currentPage} / {totalPages}
|
|
</span>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => setCurrentPage(Math.min(totalPages, currentPage + 1))}
|
|
disabled={currentPage === totalPages}
|
|
>
|
|
다음
|
|
</Button>
|
|
</div>
|
|
)}
|
|
{/* 새 화면 생성 모달 */}
|
|
<CreateScreenModal
|
|
open={isCreateOpen}
|
|
onOpenChange={setIsCreateOpen}
|
|
onCreated={(created) => {
|
|
// 목록에 즉시 반영 (첫 페이지 기준 상단 추가)
|
|
setScreens((prev) => [created, ...prev]);
|
|
}}
|
|
/>
|
|
|
|
{/* 화면 복사 모달 */}
|
|
<CopyScreenModal
|
|
isOpen={isCopyOpen}
|
|
onClose={() => setIsCopyOpen(false)}
|
|
sourceScreen={screenToCopy}
|
|
onCopySuccess={handleCopySuccess}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|