diff options
Diffstat (limited to 'app')
| -rw-r--r-- | app/[lng]/evcp/(evcp)/consent/page.tsx | 231 |
1 files changed, 41 insertions, 190 deletions
diff --git a/app/[lng]/evcp/(evcp)/consent/page.tsx b/app/[lng]/evcp/(evcp)/consent/page.tsx index 5e760b5e..3e06cb0f 100644 --- a/app/[lng]/evcp/(evcp)/consent/page.tsx +++ b/app/[lng]/evcp/(evcp)/consent/page.tsx @@ -1,14 +1,9 @@ // app/admin/policies/page.tsx (서버 컴포넌트) -import { Suspense } from 'react' import { Metadata } from 'next' import { eq, desc } from 'drizzle-orm' import db from '@/db/db' import { policyVersions } from '@/db/schema' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Badge } from '@/components/ui/badge' -import { Separator } from '@/components/ui/separator' -import { FileText, Shield, Calendar, User, Clock } from 'lucide-react' -import { PolicyManagementClient } from '@/components/polices/policy-management-client' +import { PolicyPageClient } from '@/components/polices/policy-page-client' export const metadata: Metadata = { title: '정책 관리 | eVCP Admin', @@ -18,12 +13,12 @@ export const metadata: Metadata = { // 정책 데이터 조회 함수 async function getPoliciesData() { try { - // 현재 활성 정책들 + // 현재 활성 정책들 (모든 locale) const currentPolicies = await db .select() .from(policyVersions) .where(eq(policyVersions.isCurrent, true)) - .orderBy(policyVersions.policyType) + .orderBy(policyVersions.policyType, policyVersions.locale) // 전체 정책 히스토리 const allPolicies = await db @@ -31,208 +26,64 @@ async function getPoliciesData() { .from(policyVersions) .orderBy(desc(policyVersions.createdAt)) - // 정책 타입별로 그룹화 - const policiesByType = { - privacy_policy: allPolicies.filter(p => p.policyType === 'privacy_policy'), - terms_of_service: allPolicies.filter(p => p.policyType === 'terms_of_service') + // locale별로 정책 타입별 그룹화 + const policiesByLocaleAndType = { + ko: { + privacy_policy: allPolicies.filter(p => p.policyType === 'privacy_policy' && p.locale === 'ko'), + terms_of_service: allPolicies.filter(p => p.policyType === 'terms_of_service' && p.locale === 'ko') + }, + en: { + privacy_policy: allPolicies.filter(p => p.policyType === 'privacy_policy' && p.locale === 'en'), + terms_of_service: allPolicies.filter(p => p.policyType === 'terms_of_service' && p.locale === 'en') + } } - // 현재 정책 맵 - const currentPolicyMap = {} + // 현재 정책 맵 (locale별) + const currentPolicyMap = { + ko: {}, + en: {} + } currentPolicies.forEach(policy => { - currentPolicyMap[policy.policyType] = policy + currentPolicyMap[policy.locale][policy.policyType] = policy }) return { currentPolicies: currentPolicyMap, - allPolicies: policiesByType, + allPolicies: policiesByLocaleAndType, stats: { totalVersions: allPolicies.length, - privacyVersions: policiesByType.privacy_policy.length, - termsVersions: policiesByType.terms_of_service.length, + koVersions: { + privacy: policiesByLocaleAndType.ko.privacy_policy.length, + terms: policiesByLocaleAndType.ko.terms_of_service.length + }, + enVersions: { + privacy: policiesByLocaleAndType.en.privacy_policy.length, + terms: policiesByLocaleAndType.en.terms_of_service.length + }, lastUpdate: allPolicies[0]?.createdAt || null } } } catch (error) { console.error('Failed to fetch policies:', error) return { - currentPolicies: {}, - allPolicies: { privacy_policy: [], terms_of_service: [] }, - stats: { totalVersions: 0, privacyVersions: 0, termsVersions: 0, lastUpdate: null } + currentPolicies: { ko: {}, en: {} }, + allPolicies: { + ko: { privacy_policy: [], terms_of_service: [] }, + en: { privacy_policy: [], terms_of_service: [] } + }, + stats: { + totalVersions: 0, + koVersions: { privacy: 0, terms: 0 }, + enVersions: { privacy: 0, terms: 0 }, + lastUpdate: null + } } } } + export default async function PoliciesPage() { const data = await getPoliciesData() - return ( - <div className="container mx-auto py-6 space-y-6"> - {/* 헤더 */} - <div className="flex items-center justify-between"> - <div> - <h2 className="text-2xl font-bold tracking-tight">정책 관리</h2> - <p className="text-muted-foreground"> - 개인정보 처리방침과 이용약관을 버전별로 관리합니다 - </p> - </div> - </div> - - {/* 통계 카드들 */} - <div className="grid gap-4 md:grid-cols-4"> - <Card> - <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> - <CardTitle className="text-sm font-medium">총 버전 수</CardTitle> - <FileText className="h-4 w-4 text-muted-foreground" /> - </CardHeader> - <CardContent> - <div className="text-2xl font-bold">{data.stats.totalVersions}</div> - <p className="text-xs text-muted-foreground"> - 전체 정책 버전 - </p> - </CardContent> - </Card> - - <Card> - <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> - <CardTitle className="text-sm font-medium">개인정보 정책</CardTitle> - <Shield className="h-4 w-4 text-muted-foreground" /> - </CardHeader> - <CardContent> - <div className="text-2xl font-bold">{data.stats.privacyVersions}</div> - <p className="text-xs text-muted-foreground"> - 버전 수 - </p> - </CardContent> - </Card> - - <Card> - <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> - <CardTitle className="text-sm font-medium">이용약관</CardTitle> - <FileText className="h-4 w-4 text-muted-foreground" /> - </CardHeader> - <CardContent> - <div className="text-2xl font-bold">{data.stats.termsVersions}</div> - <p className="text-xs text-muted-foreground"> - 버전 수 - </p> - </CardContent> - </Card> - - <Card> - <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> - <CardTitle className="text-sm font-medium">최근 업데이트</CardTitle> - <Clock className="h-4 w-4 text-muted-foreground" /> - </CardHeader> - <CardContent> - <div className="text-2xl font-bold"> - {data.stats.lastUpdate - ? new Date(data.stats.lastUpdate).toLocaleDateString('ko-KR') - : 'N/A' - } - </div> - <p className="text-xs text-muted-foreground"> - 마지막 정책 변경 - </p> - </CardContent> - </Card> - </div> - - {/* 현재 활성 정책들 */} - <div className="grid gap-6 md:grid-cols-2"> - <CurrentPolicyCard - title="개인정보 처리방침" - icon={<Shield className="h-5 w-5" />} - policy={data.currentPolicies.privacy_policy} - type="privacy_policy" - /> - <CurrentPolicyCard - title="이용약관" - icon={<FileText className="h-5 w-5" />} - policy={data.currentPolicies.terms_of_service} - type="terms_of_service" - /> - </div> - - <Separator /> - - {/* 클라이언트 컴포넌트로 편집 기능 제공 */} - <Suspense fallback={<PolicyManagementSkeleton />}> - <PolicyManagementClient initialData={data} /> - </Suspense> - </div> - ) -} - -// 현재 정책 카드 컴포넌트 -function CurrentPolicyCard({ title, icon, policy, type }) { - if (!policy) { - return ( - <Card> - <CardHeader> - <CardTitle className="flex items-center gap-2"> - {icon} - {title} - </CardTitle> - </CardHeader> - <CardContent> - <div className="text-center py-8 text-muted-foreground"> - <p>아직 등록된 정책이 없습니다</p> - <p className="text-sm mt-2">새 버전을 생성해주세요</p> - </div> - </CardContent> - </Card> - ) - } - - return ( - <Card> - <CardHeader> - <CardTitle className="flex items-center gap-2"> - {icon} - {title} - <Badge variant="secondary">v{policy.version}</Badge> - </CardTitle> - <CardDescription> - 현재 활성 정책 • 시행일: {new Date(policy.effectiveDate).toLocaleDateString('ko-KR')} - </CardDescription> - </CardHeader> - <CardContent> - <div className="space-y-3"> - {/* 정책 내용 미리보기 */} - <div className="bg-muted/50 p-3 rounded-md text-sm max-h-32 overflow-hidden"> - <div className="line-clamp-4"> - {policy.content?.replace(/#{1,6}\s+/g, '').replace(/\*\*(.*?)\*\*/g, '$1').substring(0, 200)}... - </div> - </div> - - {/* 메타 정보 */} - <div className="flex items-center justify-between text-xs text-muted-foreground"> - <div className="flex items-center gap-1"> - <Calendar className="h-3 w-3" /> - 생성: {new Date(policy.createdAt).toLocaleDateString('ko-KR')} - </div> - <div className="flex items-center gap-1"> - <User className="h-3 w-3" /> - 관리자 - </div> - </div> - </div> - </CardContent> - </Card> - ) + return <PolicyPageClient data={data} /> } - -// 로딩 스켈레톤 -function PolicyManagementSkeleton() { - return ( - <div className="space-y-4"> - <div className="h-8 bg-muted animate-pulse rounded" /> - <div className="grid gap-4 md:grid-cols-2"> - <div className="h-32 bg-muted animate-pulse rounded" /> - <div className="h-32 bg-muted animate-pulse rounded" /> - </div> - <div className="h-96 bg-muted animate-pulse rounded" /> - </div> - ) -}
\ No newline at end of file |
