From de2ac5a2860bc25180971e7a11f852d9d44675b7 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Wed, 6 Aug 2025 04:23:40 +0000 Subject: (대표님) 정기평가, 법적검토, 정책, 가입관련 처리 및 관련 컴포넌트 추가, 메뉴 변경 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/polices/policy-management-client.tsx | 429 ++++++++++++++++++++++++ 1 file changed, 429 insertions(+) create mode 100644 components/polices/policy-management-client.tsx (limited to 'components/polices/policy-management-client.tsx') diff --git a/components/polices/policy-management-client.tsx b/components/polices/policy-management-client.tsx new file mode 100644 index 00000000..eecb82ff --- /dev/null +++ b/components/polices/policy-management-client.tsx @@ -0,0 +1,429 @@ +'use client' + +import { useState, useTransition } from 'react' +import { Button } from '@/components/ui/button' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' +import { Badge } from '@/components/ui/badge' +import { Separator } from '@/components/ui/separator' +import { Alert, AlertDescription } from '@/components/ui/alert' +import { + Plus, + Edit, + Eye, + History, + Save, + X, + Shield, + FileText, + Calendar, + Clock, + CheckCircle2, + AlertCircle, + Loader2 +} from 'lucide-react' +import { PolicyEditor } from './policy-editor' +import { PolicyPreview } from './policy-preview' +import { PolicyHistory } from './policy-history' +import { useToast } from "@/hooks/use-toast"; +import { activatePolicyVersion, createPolicyVersion, getPolicyHistory } from '@/lib/polices/service' +import { useRouter } from "next/navigation" + +interface PolicyManagementClientProps { + initialData: { + currentPolicies: Record + allPolicies: Record + stats: any + } +} + +export function PolicyManagementClient({ initialData }: PolicyManagementClientProps) { + const [currentTab, setCurrentTab] = useState('privacy_policy') + const [editingPolicy, setEditingPolicy] = useState(null) + const [viewingHistory, setViewingHistory] = useState(null) + const [previewData, setPreviewData] = useState(null) + + // ✅ 초기 데이터를 안전하게 설정 + const [policies, setPolicies] = useState(() => { + const safePolicies = { ...initialData.allPolicies } + // 각 정책 타입에 대해 빈 배열로 초기화 + if (!safePolicies.privacy_policy) safePolicies.privacy_policy = [] + if (!safePolicies.terms_of_service) safePolicies.terms_of_service = [] + return safePolicies + }) + + const [currentPolicies, setCurrentPolicies] = useState(initialData.currentPolicies || {}) + const [isPending, startTransition] = useTransition() + const { toast } = useToast(); + const router = useRouter() + + const policyTypes = [ + { + key: 'privacy_policy', + label: '개인정보 처리방침', + icon: , + description: '개인정보 수집, 이용, 보관 및 파기에 관한 정책' + }, + { + key: 'terms_of_service', + label: '이용약관', + icon: , + description: '서비스 이용 시 준수해야 할 규칙과 조건' + } + ] + + const handleCreateNew = (policyType: string) => { + setEditingPolicy(policyType) + setViewingHistory(null) + setPreviewData(null) + } + + const handleEdit = (policyType: string) => { + setEditingPolicy(policyType) + setViewingHistory(null) + setPreviewData(null) + } + + const handleViewHistory = async (policyType: string) => { + setViewingHistory(policyType) + setEditingPolicy(null) + setPreviewData(null) + + startTransition(async () => { + try { + const history = await getPolicyHistory(policyType) + setPolicies(prev => ({ + ...prev, + [policyType]: history || [] // ✅ null/undefined 방지 + })) + } catch (error) { + console.error('Policy history error:', error) + toast({ + variant: "destructive", + title: "오류", + description: "정책 히스토리를 불러오는데 실패했습니다.", + }) + } + }) + } + + const handlePreview = (policyType: string, content: string, version: string) => { + setPreviewData({ + policyType, + content, + version, + effectiveDate: new Date().toISOString() + }) + setEditingPolicy(null) + setViewingHistory(null) + } + + const handleSavePolicy = async (policyType: string, version: string, content: string) => { + if (!content.trim()) { + toast({ + variant: "destructive", + title: "오류", + description: "정책 내용을 입력해주세요.", + }) + return + } + + startTransition(async () => { + try { + console.log('Saving policy:', { policyType, version }) // ✅ 디버깅 로그 + + const result = await createPolicyVersion({ + policyType: policyType as 'privacy_policy' | 'terms_of_service', + version, + content, + effectiveDate: new Date() + }) + + console.log('Save result:', result) // ✅ 디버깅 로그 + + if (result.success) { + toast({ + title: "성공", + description: "새 정책 버전이 생성되었습니다.", + }) + + // ✅ 상태 업데이트 - 안전하게 처리 + const newPolicy = result.policy + + setPolicies(prev => { + console.log('Updating policies state:', { prev, policyType, newPolicy }) // 디버깅 로그 + return { + ...prev, + [policyType]: [newPolicy, ...(prev[policyType] || [])] // ✅ 안전한 스프레드 + } + }) + + setCurrentPolicies(prev => { + console.log('Updating current policies state:', { prev, policyType, newPolicy }) // 디버깅 로그 + return { + ...prev, + [policyType]: newPolicy + } + }) + + setEditingPolicy(null) + + // ✅ Router refresh를 상태 업데이트 후에 호출 + router.refresh() + } else { + throw new Error(result.error) + } + } catch (error) { + console.error('Save policy error:', error) // ✅ 에러 로그 + toast({ + variant: "destructive", + title: "오류", + description: error?.message || "정책 저장에 실패했습니다.", + }) + } + }) + } + + const handleActivatePolicy = async (policyId: number, policyType: string) => { + startTransition(async () => { + try { + const result = await activatePolicyVersion(policyId) + + if (result.success) { + toast({ + title: "성공", + description: "정책이 활성화되었습니다.", + }) + + // ✅ 현재 정책 업데이트 - 안전하게 처리 + const activatedPolicy = (policies[policyType] || []).find(p => p.id === policyId) + if (activatedPolicy) { + setCurrentPolicies(prev => ({ + ...prev, + [policyType]: activatedPolicy + })) + + // ✅ 정책 목록의 isCurrent 상태 업데이트 + setPolicies(prev => ({ + ...prev, + [policyType]: (prev[policyType] || []).map(p => ({ + ...p, + isCurrent: p.id === policyId + })) + })) + } + + router.refresh() + } else { + throw new Error(result.error) + } + } catch (error) { + console.error('Activate policy error:', error) + toast({ + variant: "destructive", + title: "오류", + description: error?.message || "정책 활성화에 실패했습니다.", + }) + } + }) + } + + const renderMainContent = () => { + const currentPolicy = currentPolicies[currentTab] + const policyInfo = policyTypes.find(p => p.key === currentTab) + + // ✅ 디버깅 정보 + console.log('Render main content:', { + currentTab, + currentPolicy, + editingPolicy, + policiesForTab: policies[currentTab]?.length || 0 + }) + + if (editingPolicy === currentTab) { + return ( + setEditingPolicy(null)} + onPreview={handlePreview} + isLoading={isPending} + /> + ) + } + + if (viewingHistory === currentTab) { + return ( + setEditingPolicy(currentTab)} + onClose={() => setViewingHistory(null)} + isLoading={isPending} + /> + ) + } + + if (previewData && previewData.policyType === currentTab) { + return ( + handleSavePolicy(previewData.policyType, previewData.version, previewData.content)} + onEdit={() => setEditingPolicy(currentTab)} + onClose={() => setPreviewData(null)} + isLoading={isPending} + /> + ) + } + + // 기본 정책 관리 화면 + return ( + + +
+
+ + {policyInfo?.icon} + {policyInfo?.label} + + + {policyInfo?.description} + +
+
+ + {currentPolicy && ( + <> + + + + )} +
+
+
+ + {currentPolicy ? ( +
+ {/* 현재 정책 정보 */} +
+
+ +
+

+ 현재 활성 버전: v{currentPolicy.version} +

+

+ 시행일: {new Date(currentPolicy.effectiveDate).toLocaleDateString('ko-KR')} +

+
+
+ + 활성 + +
+ + {/* 정책 내용 미리보기 */} +
+

정책 내용 미리보기

+
+
1000 ? '...' : '') + }} + /> +
+
+ + {/* 메타 정보 */} +
+
+ + 생성일: {new Date(currentPolicy.createdAt).toLocaleString('ko-KR')} +
+
+ + 버전: {currentPolicy.version} +
+
+
+ ) : ( +
+ +

정책이 등록되지 않았습니다

+

+ {policyInfo?.label}의 첫 번째 버전을 생성해주세요. +

+ +
+ )} + + + ) + } + + return ( +
+
+

정책 편집

+ {isPending && ( +
+ + 처리 중... +
+ )} +
+ + + + {policyTypes.map(policy => ( + + {policy.icon} + {policy.label} + + ))} + + + {policyTypes.map(policy => ( + + {renderMainContent()} + + ))} + +
+ ) +} \ No newline at end of file -- cgit v1.2.3