feat: 스케줄 자동 생성 기능 및 이벤트 발송 설정 추가
- 스케줄 자동 생성 관련 라우트를 추가하여 API 연동을 구현하였습니다. - 버튼 설정 패널에 이벤트 발송 옵션을 추가하여 사용자가 이벤트를 설정할 수 있도록 하였습니다. - 타임라인 스케줄러 컴포넌트에서 스케줄 데이터 필터링 및 선택된 품목에 따른 스케줄 로드 기능을 개선하였습니다. - 이벤트 버스를 통해 다른 컴포넌트와의 상호작용을 강화하였습니다. - 관련 문서 및 주석을 업데이트하여 새로운 기능에 대한 이해를 돕도록 하였습니다.
This commit is contained in:
208
frontend/lib/v2-core/services/ScheduleConfirmDialog.tsx
Normal file
208
frontend/lib/v2-core/services/ScheduleConfirmDialog.tsx
Normal file
@@ -0,0 +1,208 @@
|
||||
/**
|
||||
* 스케줄 생성 확인 다이얼로그
|
||||
*
|
||||
* 스케줄 자동 생성 시 미리보기 결과를 표시하고 확인을 받는 다이얼로그입니다.
|
||||
*/
|
||||
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { Calendar, Plus, Trash2, RefreshCw } from "lucide-react";
|
||||
import type { SchedulePreviewResult } from "./ScheduleGeneratorService";
|
||||
|
||||
interface ScheduleConfirmDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
preview: SchedulePreviewResult | null;
|
||||
onConfirm: () => void;
|
||||
onCancel: () => void;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
export function ScheduleConfirmDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
preview,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
isLoading = false,
|
||||
}: ScheduleConfirmDialogProps) {
|
||||
if (!preview) return null;
|
||||
|
||||
const { summary, toCreate, toDelete, toUpdate } = preview;
|
||||
|
||||
return (
|
||||
<AlertDialog open={open} onOpenChange={onOpenChange}>
|
||||
<AlertDialogContent className="max-w-[95vw] sm:max-w-[600px]">
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle className="flex items-center gap-2 text-base sm:text-lg">
|
||||
<Calendar className="h-5 w-5" />
|
||||
스케줄 생성 확인
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="text-xs sm:text-sm">
|
||||
다음과 같이 스케줄이 변경됩니다. 계속하시겠습니까?
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
|
||||
{/* 요약 정보 */}
|
||||
<div className="grid grid-cols-3 gap-3 py-4">
|
||||
<div className="flex flex-col items-center rounded-lg border bg-green-50 p-3 dark:bg-green-900/20">
|
||||
<Plus className="mb-1 h-5 w-5 text-green-600" />
|
||||
<span className="text-2xl font-bold text-green-600">
|
||||
{summary.createCount}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">생성</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center rounded-lg border bg-red-50 p-3 dark:bg-red-900/20">
|
||||
<Trash2 className="mb-1 h-5 w-5 text-red-600" />
|
||||
<span className="text-2xl font-bold text-red-600">
|
||||
{summary.deleteCount}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">삭제</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center rounded-lg border bg-blue-50 p-3 dark:bg-blue-900/20">
|
||||
<RefreshCw className="mb-1 h-5 w-5 text-blue-600" />
|
||||
<span className="text-2xl font-bold text-blue-600">
|
||||
{summary.updateCount}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">수정</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 상세 정보 */}
|
||||
<ScrollArea className="max-h-[300px]">
|
||||
<div className="space-y-4">
|
||||
{/* 생성될 스케줄 */}
|
||||
{toCreate.length > 0 && (
|
||||
<div>
|
||||
<h4 className="mb-2 flex items-center gap-2 text-sm font-medium">
|
||||
<Badge variant="default" className="bg-green-600">
|
||||
생성
|
||||
</Badge>
|
||||
{toCreate.length}건
|
||||
</h4>
|
||||
<div className="space-y-1 rounded-md border p-2">
|
||||
{toCreate.slice(0, 5).map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-center justify-between text-xs"
|
||||
>
|
||||
<span className="font-medium">
|
||||
{item.resource_name || item.resource_id}
|
||||
</span>
|
||||
<span className="text-muted-foreground">
|
||||
{item.start_date} ~ {item.end_date} / {item.plan_qty}개
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
{toCreate.length > 5 && (
|
||||
<div className="text-center text-xs text-muted-foreground">
|
||||
... 외 {toCreate.length - 5}건
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 삭제될 스케줄 */}
|
||||
{toDelete.length > 0 && (
|
||||
<div>
|
||||
<h4 className="mb-2 flex items-center gap-2 text-sm font-medium">
|
||||
<Badge variant="destructive">삭제</Badge>
|
||||
{toDelete.length}건
|
||||
</h4>
|
||||
<div className="space-y-1 rounded-md border border-red-200 bg-red-50/50 p-2 dark:border-red-800 dark:bg-red-900/10">
|
||||
{toDelete.slice(0, 5).map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-center justify-between text-xs"
|
||||
>
|
||||
<span className="font-medium">
|
||||
{item.resource_name || item.resource_id}
|
||||
</span>
|
||||
<span className="text-muted-foreground">
|
||||
{item.start_date} ~ {item.end_date}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
{toDelete.length > 5 && (
|
||||
<div className="text-center text-xs text-muted-foreground">
|
||||
... 외 {toDelete.length - 5}건
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 수정될 스케줄 */}
|
||||
{toUpdate.length > 0 && (
|
||||
<div>
|
||||
<h4 className="mb-2 flex items-center gap-2 text-sm font-medium">
|
||||
<Badge variant="secondary">수정</Badge>
|
||||
{toUpdate.length}건
|
||||
</h4>
|
||||
<div className="space-y-1 rounded-md border border-blue-200 bg-blue-50/50 p-2 dark:border-blue-800 dark:bg-blue-900/10">
|
||||
{toUpdate.slice(0, 5).map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-center justify-between text-xs"
|
||||
>
|
||||
<span className="font-medium">
|
||||
{item.resource_name || item.resource_id}
|
||||
</span>
|
||||
<span className="text-muted-foreground">
|
||||
{item.start_date} ~ {item.end_date}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
{toUpdate.length > 5 && (
|
||||
<div className="text-center text-xs text-muted-foreground">
|
||||
... 외 {toUpdate.length - 5}건
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
|
||||
{/* 총 수량 */}
|
||||
<div className="flex items-center justify-between rounded-md bg-muted p-3">
|
||||
<span className="text-sm font-medium">총 계획 수량</span>
|
||||
<span className="text-lg font-bold">
|
||||
{summary.totalQty.toLocaleString()}개
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<AlertDialogFooter className="gap-2 sm:gap-0">
|
||||
<AlertDialogCancel
|
||||
onClick={onCancel}
|
||||
disabled={isLoading}
|
||||
className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
|
||||
>
|
||||
취소
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={onConfirm}
|
||||
disabled={isLoading}
|
||||
className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
|
||||
>
|
||||
{isLoading ? "처리 중..." : "확인 및 적용"}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user