refactor: ConfirmDialog 추가 적용 및 일정 제목 truncate 제거
수정된 파일: - AdminScheduleCategory.jsx (50줄 → 15줄, 35줄 제거) - AdminAlbumPhotos.jsx (60줄 → 15줄, 45줄 제거) - AdminScheduleForm.jsx (55줄 → 12줄, 43줄 제거) - AdminSchedule.jsx 일정 제목 truncate 2곳 제거 총 약 220줄의 중복 코드 제거
This commit is contained in:
parent
d52b43397c
commit
8e01692d6e
4 changed files with 47 additions and 165 deletions
|
|
@ -4,11 +4,12 @@ import { motion, AnimatePresence, Reorder } from 'framer-motion';
|
||||||
import {
|
import {
|
||||||
Upload, Trash2, Image, X, Check, Plus,
|
Upload, Trash2, Image, X, Check, Plus,
|
||||||
Home, ChevronRight, ArrowLeft, Grid, List,
|
Home, ChevronRight, ArrowLeft, Grid, List,
|
||||||
ZoomIn, AlertTriangle, GripVertical, Users, User, Users2,
|
ZoomIn, GripVertical, Users, User, Users2,
|
||||||
Tag, FolderOpen, Save
|
Tag, FolderOpen, Save
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import Toast from '../../../components/Toast';
|
import Toast from '../../../components/Toast';
|
||||||
import AdminHeader from '../../../components/admin/AdminHeader';
|
import AdminHeader from '../../../components/admin/AdminHeader';
|
||||||
|
import ConfirmDialog from '../../../components/admin/ConfirmDialog';
|
||||||
import useToast from '../../../hooks/useToast';
|
import useToast from '../../../hooks/useToast';
|
||||||
import * as authApi from '../../../api/admin/auth';
|
import * as authApi from '../../../api/admin/auth';
|
||||||
import { getAlbum } from '../../../api/public/albums';
|
import { getAlbum } from '../../../api/public/albums';
|
||||||
|
|
@ -530,65 +531,20 @@ function AdminAlbumPhotos() {
|
||||||
<Toast toast={toast} onClose={() => setToast(null)} />
|
<Toast toast={toast} onClose={() => setToast(null)} />
|
||||||
|
|
||||||
{/* 삭제 확인 다이얼로그 */}
|
{/* 삭제 확인 다이얼로그 */}
|
||||||
<AnimatePresence>
|
<ConfirmDialog
|
||||||
{deleteDialog.show && (
|
isOpen={deleteDialog.show}
|
||||||
<motion.div
|
onClose={() => setDeleteDialog({ show: false, photos: [] })}
|
||||||
initial={{ opacity: 0 }}
|
onConfirm={handleDelete}
|
||||||
animate={{ opacity: 1 }}
|
title="사진 삭제"
|
||||||
exit={{ opacity: 0 }}
|
message={
|
||||||
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
|
<>
|
||||||
onClick={() => !deleting && setDeleteDialog({ show: false, photos: [] })}
|
<span className="font-medium text-gray-900">{deleteDialog.photos.length}개</span>의 사진을 삭제하시겠습니까?
|
||||||
>
|
<br />
|
||||||
<motion.div
|
<span className="text-sm text-red-500">이 작업은 되돌릴 수 없습니다.</span>
|
||||||
initial={{ scale: 0.9, opacity: 0 }}
|
</>
|
||||||
animate={{ scale: 1, opacity: 1 }}
|
}
|
||||||
exit={{ scale: 0.9, opacity: 0 }}
|
loading={deleting}
|
||||||
className="bg-white rounded-2xl p-6 max-w-md w-full mx-4 shadow-xl"
|
/>
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-3 mb-4">
|
|
||||||
<div className="w-10 h-10 rounded-full bg-red-100 flex items-center justify-center">
|
|
||||||
<AlertTriangle className="text-red-500" size={20} />
|
|
||||||
</div>
|
|
||||||
<h3 className="text-lg font-bold text-gray-900">사진 삭제</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p className="text-gray-600 mb-6">
|
|
||||||
<span className="font-medium text-gray-900">{deleteDialog.photos.length}개</span>의 사진을 삭제하시겠습니까?
|
|
||||||
<br />
|
|
||||||
<span className="text-sm text-red-500">이 작업은 되돌릴 수 없습니다.</span>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="flex justify-end gap-3">
|
|
||||||
<button
|
|
||||||
onClick={() => setDeleteDialog({ show: false, photos: [] })}
|
|
||||||
disabled={deleting}
|
|
||||||
className="px-4 py-2 text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors disabled:opacity-50"
|
|
||||||
>
|
|
||||||
취소
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={handleDelete}
|
|
||||||
disabled={deleting}
|
|
||||||
className="px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors disabled:opacity-50 flex items-center gap-2"
|
|
||||||
>
|
|
||||||
{deleting ? (
|
|
||||||
<>
|
|
||||||
<span className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" />
|
|
||||||
삭제 중...
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Trash2 size={16} />
|
|
||||||
삭제
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
|
|
||||||
{/* 이미지 미리보기 */}
|
{/* 이미지 미리보기 */}
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ const ScheduleItem = memo(function ScheduleItem({
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<h3 className="font-semibold text-gray-900 truncate">{schedule.title}</h3>
|
<h3 className="font-semibold text-gray-900">{schedule.title}</h3>
|
||||||
<div className="flex items-center gap-3 mt-1 text-sm text-gray-500">
|
<div className="flex items-center gap-3 mt-1 text-sm text-gray-500">
|
||||||
{schedule.time && (
|
{schedule.time && (
|
||||||
<span className="flex items-center gap-1">
|
<span className="flex items-center gap-1">
|
||||||
|
|
@ -1103,7 +1103,7 @@ function AdminSchedule() {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<h3 className="font-semibold text-gray-900 truncate">{schedule.title}</h3>
|
<h3 className="font-semibold text-gray-900">{schedule.title}</h3>
|
||||||
<div className="flex items-center gap-3 mt-1 text-sm text-gray-500">
|
<div className="flex items-center gap-3 mt-1 text-sm text-gray-500">
|
||||||
{schedule.time && (
|
{schedule.time && (
|
||||||
<span className="flex items-center gap-1">
|
<span className="flex items-center gap-1">
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
import { motion, AnimatePresence, Reorder } from 'framer-motion';
|
import { motion, AnimatePresence, Reorder } from 'framer-motion';
|
||||||
import { Home, ChevronRight, Plus, Edit3, Trash2, GripVertical, X, AlertTriangle } from 'lucide-react';
|
import { Home, ChevronRight, Plus, Edit3, Trash2, GripVertical, X } from 'lucide-react';
|
||||||
import { HexColorPicker } from 'react-colorful';
|
import { HexColorPicker } from 'react-colorful';
|
||||||
import Toast from '../../../components/Toast';
|
import Toast from '../../../components/Toast';
|
||||||
import AdminHeader from '../../../components/admin/AdminHeader';
|
import AdminHeader from '../../../components/admin/AdminHeader';
|
||||||
|
import ConfirmDialog from '../../../components/admin/ConfirmDialog';
|
||||||
import useToast from '../../../hooks/useToast';
|
import useToast from '../../../hooks/useToast';
|
||||||
import * as authApi from '../../../api/admin/auth';
|
import * as authApi from '../../../api/admin/auth';
|
||||||
import * as categoriesApi from '../../../api/admin/categories';
|
import * as categoriesApi from '../../../api/admin/categories';
|
||||||
|
|
@ -446,54 +447,19 @@ function AdminScheduleCategory() {
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
|
|
||||||
{/* 삭제 확인 다이얼로그 */}
|
{/* 삭제 확인 다이얼로그 */}
|
||||||
<AnimatePresence>
|
<ConfirmDialog
|
||||||
{deleteDialogOpen && (
|
isOpen={deleteDialogOpen}
|
||||||
<motion.div
|
onClose={() => setDeleteDialogOpen(false)}
|
||||||
initial={{ opacity: 0 }}
|
onConfirm={handleDelete}
|
||||||
animate={{ opacity: 1 }}
|
title="카테고리 삭제"
|
||||||
exit={{ opacity: 0 }}
|
message={
|
||||||
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
|
<>
|
||||||
onClick={() => setDeleteDialogOpen(false)}
|
<span className="font-medium text-gray-900">"{deleteTarget?.name}"</span> 카테고리를 삭제하시겠습니까?
|
||||||
>
|
<br />
|
||||||
<motion.div
|
<span className="text-sm text-red-500">이 작업은 되돌릴 수 없습니다.</span>
|
||||||
initial={{ scale: 0.9, opacity: 0 }}
|
</>
|
||||||
animate={{ scale: 1, opacity: 1 }}
|
}
|
||||||
exit={{ scale: 0.9, opacity: 0 }}
|
/>
|
||||||
className="bg-white rounded-2xl p-6 max-w-md w-full mx-4 shadow-xl"
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-3 mb-4">
|
|
||||||
<div className="w-10 h-10 rounded-full bg-red-100 flex items-center justify-center">
|
|
||||||
<AlertTriangle className="text-red-500" size={20} />
|
|
||||||
</div>
|
|
||||||
<h3 className="text-lg font-bold text-gray-900">카테고리 삭제</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p className="text-gray-600 mb-6">
|
|
||||||
<span className="font-medium text-gray-900">"{deleteTarget?.name}"</span> 카테고리를 삭제하시겠습니까?
|
|
||||||
<br />
|
|
||||||
<span className="text-sm text-red-500">이 작업은 되돌릴 수 없습니다.</span>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="flex justify-end gap-3">
|
|
||||||
<button
|
|
||||||
onClick={() => setDeleteDialogOpen(false)}
|
|
||||||
className="px-4 py-2 text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
|
|
||||||
>
|
|
||||||
취소
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={handleDelete}
|
|
||||||
className="px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors flex items-center gap-2"
|
|
||||||
>
|
|
||||||
<Trash2 size={16} />
|
|
||||||
삭제
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import { useNavigate, Link, useParams } from "react-router-dom";
|
||||||
import { motion, AnimatePresence } from "framer-motion";
|
import { motion, AnimatePresence } from "framer-motion";
|
||||||
import { formatDate } from "../../../utils/date";
|
import { formatDate } from "../../../utils/date";
|
||||||
import {
|
import {
|
||||||
LogOut,
|
|
||||||
Home,
|
Home,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
Calendar,
|
Calendar,
|
||||||
|
|
@ -20,7 +19,6 @@ import {
|
||||||
Plus,
|
Plus,
|
||||||
MapPin,
|
MapPin,
|
||||||
Settings,
|
Settings,
|
||||||
AlertTriangle,
|
|
||||||
Trash2,
|
Trash2,
|
||||||
Search,
|
Search,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
@ -29,6 +27,7 @@ import Lightbox from "../../../components/common/Lightbox";
|
||||||
import CustomDatePicker from "../../../components/admin/CustomDatePicker";
|
import CustomDatePicker from "../../../components/admin/CustomDatePicker";
|
||||||
import CustomTimePicker from "../../../components/admin/CustomTimePicker";
|
import CustomTimePicker from "../../../components/admin/CustomTimePicker";
|
||||||
import AdminHeader from "../../../components/admin/AdminHeader";
|
import AdminHeader from "../../../components/admin/AdminHeader";
|
||||||
|
import ConfirmDialog from "../../../components/admin/ConfirmDialog";
|
||||||
import useToast from "../../../hooks/useToast";
|
import useToast from "../../../hooks/useToast";
|
||||||
import * as authApi from "../../../api/admin/auth";
|
import * as authApi from "../../../api/admin/auth";
|
||||||
import * as categoriesApi from "../../../api/admin/categories";
|
import * as categoriesApi from "../../../api/admin/categories";
|
||||||
|
|
@ -513,58 +512,19 @@ function AdminScheduleForm() {
|
||||||
<Toast toast={toast} onClose={() => setToast(null)} />
|
<Toast toast={toast} onClose={() => setToast(null)} />
|
||||||
|
|
||||||
{/* 삭제 확인 다이얼로그 */}
|
{/* 삭제 확인 다이얼로그 */}
|
||||||
<AnimatePresence>
|
<ConfirmDialog
|
||||||
{deleteDialogOpen && (
|
isOpen={deleteDialogOpen}
|
||||||
<motion.div
|
onClose={() => setDeleteDialogOpen(false)}
|
||||||
initial={{ opacity: 0 }}
|
onConfirm={confirmDeleteImage}
|
||||||
animate={{ opacity: 1 }}
|
title="이미지 삭제"
|
||||||
exit={{ opacity: 0 }}
|
message={
|
||||||
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
|
<>
|
||||||
onClick={() => setDeleteDialogOpen(false)}
|
이 이미지를 삭제하시겠습니까?
|
||||||
>
|
<br />
|
||||||
<motion.div
|
<span className="text-sm text-red-500">이 작업은 되돌릴 수 없습니다.</span>
|
||||||
initial={{ scale: 0.9, opacity: 0 }}
|
</>
|
||||||
animate={{ scale: 1, opacity: 1 }}
|
}
|
||||||
exit={{ scale: 0.9, opacity: 0 }}
|
/>
|
||||||
className="bg-white rounded-2xl p-6 max-w-md w-full mx-4 shadow-xl"
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-3 mb-4">
|
|
||||||
<div className="w-10 h-10 rounded-full bg-red-100 flex items-center justify-center">
|
|
||||||
<AlertTriangle className="text-red-500" size={20} />
|
|
||||||
</div>
|
|
||||||
<h3 className="text-lg font-bold text-gray-900">이미지 삭제</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p className="text-gray-600 mb-6">
|
|
||||||
이 이미지를 삭제하시겠습니까?
|
|
||||||
<br />
|
|
||||||
<span className="text-sm text-red-500">
|
|
||||||
이 작업은 되돌릴 수 없습니다.
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="flex justify-end gap-3">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setDeleteDialogOpen(false)}
|
|
||||||
className="px-4 py-2 text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
|
|
||||||
>
|
|
||||||
취소
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={confirmDeleteImage}
|
|
||||||
className="px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors flex items-center gap-2"
|
|
||||||
>
|
|
||||||
<Trash2 size={16} />
|
|
||||||
삭제
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
|
|
||||||
{/* 장소 검색 다이얼로그 */}
|
{/* 장소 검색 다이얼로그 */}
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue