diff options
Diffstat (limited to 'components/polices/policy-history.tsx')
| -rw-r--r-- | components/polices/policy-history.tsx | 250 |
1 files changed, 250 insertions, 0 deletions
diff --git a/components/polices/policy-history.tsx b/components/polices/policy-history.tsx new file mode 100644 index 00000000..af6a68f2 --- /dev/null +++ b/components/polices/policy-history.tsx @@ -0,0 +1,250 @@ +import { useState, useEffect } from 'react' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { Alert, AlertDescription } from '@/components/ui/alert' +import { Separator } from '@/components/ui/separator' +import { Save, Eye, X, Info, AlertTriangle, Calendar, Clock } from 'lucide-react' +import { Badge } from '../ui/badge' +import { PolicyPreview } from './policy-preview' + +// ✅ 타입 정의 +interface PolicyData { + id: number + policyType: 'privacy_policy' | 'terms_of_service' + version: string + content: string + effectiveDate: string + isCurrent: boolean + createdAt: string +} + +interface PolicyHistoryProps { + policyType: 'privacy_policy' | 'terms_of_service' + policies: PolicyData[] | null | undefined // ✅ null/undefined 허용 + currentPolicy?: PolicyData | null + onActivate: (policyId: number, policyType: string) => void + onEdit: () => void + onClose: () => void + isLoading?: boolean +} + +export function PolicyHistory({ + policyType, + policies, + currentPolicy, + onActivate, + onEdit, + onClose, + isLoading = false +}: PolicyHistoryProps) { + const [viewingPolicy, setViewingPolicy] = useState<PolicyData | null>(null) // ✅ 상세보기 상태 + + const policyLabels: Record<string, string> = { + privacy_policy: '개인정보 처리방침', + terms_of_service: '이용약관' + } + + // ✅ 디버깅 로그 + console.log('PolicyHistory - policies:', policies, 'type:', typeof policies, 'isArray:', Array.isArray(policies)) + + // ✅ 안전한 배열 변환 + const safePolicies = Array.isArray(policies) ? policies :Array.isArray(policies.data) ? policies.data : [] + + // ✅ 내용 미리보기 함수 + const getContentPreview = (content: string): string => { + if (!content) return '내용 없음' + + // HTML 태그 제거 및 텍스트 추출 + const textContent = content.replace(/<[^>]*>/g, '').trim() + return textContent.length > 200 + ? textContent.substring(0, 200) + '...' + : textContent + } + + // ✅ 상세보기 핸들러 + const handleViewDetail = (policy: PolicyData) => { + setViewingPolicy(policy) + } + + // ✅ 상세보기 닫기 핸들러 + const handleCloseDetail = () => { + setViewingPolicy(null) + } + + // ✅ 상세보기 모드일 때 PolicyPreview 렌더링 + if (viewingPolicy) { + return ( + <PolicyPreview + data={{ + policyType: viewingPolicy.policyType, + content: viewingPolicy.content, + version: viewingPolicy.version, + effectiveDate: viewingPolicy.effectiveDate, + id: viewingPolicy.id, + isCurrent: viewingPolicy.isCurrent, + createdAt: viewingPolicy.createdAt + }} + onClose={handleCloseDetail} + mode="view" + /> + ) + } + + return ( + <Card> + <CardHeader> + <div className="flex items-center justify-between"> + <div> + <CardTitle className="flex items-center gap-2"> + <Clock className="h-5 w-5" /> + 정책 히스토리 + </CardTitle> + <CardDescription> + {policyLabels[policyType]}의 모든 버전을 확인하고 관리합니다 + </CardDescription> + </div> + <div className="flex gap-2"> + <Button + variant="outline" + size="sm" + onClick={onEdit} + disabled={isLoading} + > + 새 버전 생성 + </Button> + <Button + variant="outline" + size="sm" + onClick={onClose} + disabled={isLoading} + > + <X className="h-4 w-4" /> + </Button> + </div> + </div> + </CardHeader> + <CardContent> + {/* ✅ 로딩 상태 */} + {isLoading && ( + <div className="flex items-center justify-center py-8"> + <div className="text-muted-foreground">히스토리를 불러오는 중...</div> + </div> + )} + + {/* ✅ 에러 상태 체크 */} + {!isLoading && safePolicies && !Array.isArray(safePolicies) && ( + <Alert variant="destructive" className="mb-4"> + <AlertTriangle className="h-4 w-4" /> + <AlertDescription> + 정책 데이터 형식이 올바르지 않습니다. (받은 데이터: {typeof safePolicies}) + </AlertDescription> + </Alert> + )} + + {/* ✅ 정책 목록 */} + {!isLoading && ( + <div className="space-y-4"> + {safePolicies.length === 0 ? ( + <div className="text-center py-8"> + <div className="text-muted-foreground mb-2"> + <Info className="h-8 w-8 mx-auto mb-2 opacity-50" /> + <p>등록된 정책 버전이 없습니다.</p> + </div> + <Button onClick={onEdit} size="sm"> + 첫 번째 버전 생성하기 + </Button> + </div> + ) : ( + <> + {/* ✅ 정책 개수 표시 */} + <div className="flex items-center justify-between mb-4"> + <div className="text-sm text-muted-foreground"> + 총 {safePolicies.length}개 버전 + </div> + {currentPolicy && ( + <Badge variant="outline" className="text-xs"> + 현재: v{currentPolicy.version} + </Badge> + )} + </div> + + {/* ✅ 정책 목록 렌더링 */} + {safePolicies.map((policy: PolicyData) => ( + <div + key={policy.id} + className={`p-4 border rounded-lg transition-colors ${ + policy.isCurrent + ? 'border-green-200 bg-green-50' + : 'border-border hover:bg-muted/30' + }`} + > + <div className="flex items-start justify-between"> + <div className="flex-1 min-w-0"> + {/* ✅ 헤더 */} + <div className="flex items-center gap-2 mb-2"> + <h4 className="font-medium text-base"> + 버전 {policy.version} + </h4> + {policy.isCurrent && ( + <Badge className="bg-green-100 text-green-800 text-xs"> + 현재 활성 + </Badge> + )} + </div> + + {/* ✅ 메타 정보 */} + <div className="grid grid-cols-1 md:grid-cols-2 gap-2 mb-3 text-sm text-muted-foreground"> + <div className="flex items-center gap-1"> + <Calendar className="h-3 w-3" /> + 시행일: {new Date(policy.effectiveDate).toLocaleDateString('ko-KR')} + </div> + <div className="flex items-center gap-1"> + <Clock className="h-3 w-3" /> + 생성일: {new Date(policy.createdAt).toLocaleDateString('ko-KR')} + </div> + </div> + + {/* ✅ 내용 미리보기 */} + <div className="mt-2"> + <div className="text-sm bg-muted/50 p-3 rounded border max-h-20 overflow-hidden"> + {getContentPreview(policy.content)} + </div> + </div> + </div> + + {/* ✅ 액션 버튼들 */} + <div className="flex flex-col gap-2 ml-4 flex-shrink-0"> + {!policy.isCurrent && ( + <Button + size="sm" + onClick={() => onActivate(policy.id, policyType)} + disabled={isLoading} + className="whitespace-nowrap" + > + 활성화 + </Button> + )} + <Button + variant="outline" + size="sm" + onClick={() => handleViewDetail(policy)} + disabled={isLoading} + className="whitespace-nowrap" + > + <Eye className="h-3 w-3 mr-1" /> + 상세보기 + </Button> + </div> + </div> + </div> + ))} + </> + )} + </div> + )} + </CardContent> + </Card> + ) +}
\ No newline at end of file |
