ERP-node/frontend/app/(main)/screens/[screenId]/page.tsx

277 lines
11 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useEffect, useState } from "react";
import { useParams } from "next/navigation";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { ArrowLeft, Save, Loader2 } from "lucide-react";
import { screenApi } from "@/lib/api/screen";
import { ScreenDefinition, LayoutData } from "@/types/screen";
import { InteractiveScreenViewer } from "@/components/screen/InteractiveScreenViewer";
import { useRouter } from "next/navigation";
import { toast } from "sonner";
export default function ScreenViewPage() {
const params = useParams();
const router = useRouter();
const screenId = parseInt(params.screenId as string);
const [screen, setScreen] = useState<ScreenDefinition | null>(null);
const [layout, setLayout] = useState<LayoutData | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [saving, setSaving] = useState(false);
const [formData, setFormData] = useState<Record<string, any>>({});
useEffect(() => {
const loadScreen = async () => {
try {
setLoading(true);
setError(null);
// 화면 정보 로드
const screenData = await screenApi.getScreen(screenId);
setScreen(screenData);
// 레이아웃 로드
try {
const layoutData = await screenApi.getLayout(screenId);
setLayout(layoutData);
} catch (layoutError) {
console.warn("레이아웃 로드 실패, 빈 레이아웃 사용:", layoutError);
setLayout({
components: [],
gridSettings: { columns: 12, gap: 16, padding: 16 },
});
}
} catch (error) {
console.error("화면 로드 실패:", error);
setError("화면을 불러오는데 실패했습니다.");
toast.error("화면을 불러오는데 실패했습니다.");
} finally {
setLoading(false);
}
};
if (screenId) {
loadScreen();
}
}, [screenId]);
// 폼 데이터 저장 함수
const handleSaveData = async () => {
if (!screen) return;
try {
setSaving(true);
console.log("저장할 데이터:", formData);
console.log("화면 정보:", screen);
// 여기에 실제 데이터 저장 API 호출을 추가할 수 있습니다
// await saveFormData(screen.tableName, formData);
toast.success("데이터가 성공적으로 저장되었습니다.");
} catch (error) {
console.error("데이터 저장 실패:", error);
toast.error("데이터 저장에 실패했습니다.");
} finally {
setSaving(false);
}
};
if (loading) {
return (
<div className="flex h-full items-center justify-center">
<div className="text-center">
<Loader2 className="mx-auto h-8 w-8 animate-spin text-blue-600" />
<p className="mt-2 text-gray-600"> ...</p>
</div>
</div>
);
}
if (error || !screen) {
return (
<div className="flex h-full items-center justify-center">
<div className="text-center">
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-red-100">
<span className="text-2xl"></span>
</div>
<h2 className="mb-2 text-xl font-semibold text-gray-900"> </h2>
<p className="mb-4 text-gray-600">{error || "요청하신 화면이 존재하지 않습니다."}</p>
<Button onClick={() => router.back()} variant="outline">
<ArrowLeft className="mr-2 h-4 w-4" />
</Button>
</div>
</div>
);
}
return (
<div className="flex h-full w-full flex-col">
{/* 헤더 */}
<div className="flex items-center justify-between border-b bg-white p-4 shadow-sm">
<div className="flex items-center space-x-4">
<Button variant="outline" size="sm" onClick={() => router.back()}>
<ArrowLeft className="mr-2 h-4 w-4" />
</Button>
<div>
<h1 className="text-xl font-semibold text-gray-900">{screen.screenName}</h1>
<div className="mt-1 flex items-center space-x-2">
<Badge variant="outline" className="font-mono text-xs">
{screen.screenCode}
</Badge>
<Badge variant="secondary" className="text-xs">
{screen.tableName}
</Badge>
<Badge variant={screen.isActive === "Y" ? "default" : "secondary"} className="text-xs">
{screen.isActive === "Y" ? "활성" : "비활성"}
</Badge>
</div>
</div>
</div>
<div className="flex items-center space-x-2">
<span className="text-sm text-gray-500">: {screen.createdDate.toLocaleDateString()}</span>
</div>
</div>
{/* 메인 컨텐츠 영역 */}
<div className="flex-1 overflow-hidden">
{layout && layout.components.length > 0 ? (
<div className="h-full p-6">
<Card className="h-full">
<CardHeader>
<CardTitle className="flex items-center justify-between">
<span>{screen.screenName}</span>
<Button
size="sm"
className="bg-blue-600 hover:bg-blue-700"
onClick={handleSaveData}
disabled={saving}
>
{saving ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
...
</>
) : (
<>
<Save className="mr-2 h-4 w-4" />
</>
)}
</Button>
</CardTitle>
{screen.description && <p className="text-sm text-gray-600">{screen.description}</p>}
</CardHeader>
<CardContent className="h-[calc(100%-5rem)] overflow-auto">
{/* 실제 화면 렌더링 영역 */}
<div className="relative h-full w-full bg-white">
{layout.components
.filter((comp) => !comp.parentId) // 최상위 컴포넌트만 렌더링 (그룹 포함)
.map((component) => {
// 그룹 컴포넌트인 경우 특별 처리
if (component.type === "group") {
const groupChildren = layout.components.filter((child) => child.parentId === component.id);
return (
<div
key={component.id}
style={{
position: "absolute",
left: `${component.position.x}px`,
top: `${component.position.y}px`,
width: `${component.size.width}px`,
height: `${component.size.height}px`,
zIndex: component.position.z || 1,
backgroundColor: (component as any).backgroundColor || "rgba(59, 130, 246, 0.1)",
border: (component as any).border || "2px dashed #3b82f6",
borderRadius: (component as any).borderRadius || "8px",
padding: "16px",
}}
>
{/* 그룹 제목 */}
{(component as any).title && (
<div className="mb-2 text-sm font-medium text-blue-700">{(component as any).title}</div>
)}
{/* 그룹 내 자식 컴포넌트들 렌더링 */}
{groupChildren.map((child) => (
<div
key={child.id}
style={{
position: "absolute",
left: `${child.position.x}px`,
top: `${child.position.y}px`,
width: `${child.size.width}px`,
height: `${child.size.height}px`,
zIndex: child.position.z || 1,
}}
>
<InteractiveScreenViewer
component={child}
allComponents={layout.components}
formData={formData}
onFormDataChange={(fieldName, value) => {
setFormData((prev) => ({
...prev,
[fieldName]: value,
}));
}}
/>
</div>
))}
</div>
);
}
// 일반 컴포넌트 렌더링
return (
<div
key={component.id}
style={{
position: "absolute",
left: `${component.position.x}px`,
top: `${component.position.y}px`,
width: `${component.size.width}px`,
height: `${component.size.height}px`,
zIndex: component.position.z || 1,
}}
>
<InteractiveScreenViewer
component={component}
allComponents={layout.components}
formData={formData}
onFormDataChange={(fieldName, value) => {
setFormData((prev) => ({
...prev,
[fieldName]: value,
}));
}}
/>
</div>
);
})}
</div>
</CardContent>
</Card>
</div>
) : (
<div className="flex h-full items-center justify-center">
<div className="text-center">
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-gray-100">
<span className="text-2xl">📄</span>
</div>
<h2 className="mb-2 text-xl font-semibold text-gray-900"> </h2>
<p className="text-gray-600"> .</p>
</div>
</div>
)}
</div>
</div>
);
}