diff options
Diffstat (limited to 'components/polices/policy-editor.tsx')
| -rw-r--r-- | components/polices/policy-editor.tsx | 262 |
1 files changed, 262 insertions, 0 deletions
diff --git a/components/polices/policy-editor.tsx b/components/polices/policy-editor.tsx new file mode 100644 index 00000000..d58831e0 --- /dev/null +++ b/components/polices/policy-editor.tsx @@ -0,0 +1,262 @@ +'use client' + +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 } from 'lucide-react' +import TiptapEditor from '../qna/tiptap-editor' + +interface PolicyEditorProps { + policyType: string + currentPolicy?: any + onSave: (policyType: string, version: string, content: string) => void + onCancel: () => void + onPreview: (policyType: string, content: string, version: string) => void + isLoading?: boolean +} + +export function PolicyEditor({ + policyType, + currentPolicy, + onSave, + onCancel, + onPreview, + isLoading = false +}: PolicyEditorProps) { + const [version, setVersion] = useState('') + const [content, setContent] = useState('') + const [validationErrors, setValidationErrors] = useState<string[]>([]) + + console.log(content) + + const policyLabels = { + privacy_policy: '개인정보 처리방침', + terms_of_service: '이용약관' + } + + // 현재 정책 기반으로 다음 버전 생성 + useEffect(() => { + if (currentPolicy) { + const currentVersion = currentPolicy.version + const versionParts = currentVersion.split('.') + const majorVersion = parseInt(versionParts[0]) || 1 + const minorVersion = parseInt(versionParts[1]) || 0 + + // 마이너 버전 업 + const nextVersion = `${majorVersion}.${minorVersion + 1}` + setVersion(nextVersion) + setContent(currentPolicy.content || '') + } else { + setVersion('1.0') + setContent(getDefaultPolicyContent(policyType)) + } + }, [currentPolicy, policyType]) + + const getDefaultPolicyContent = (type: string) => { + if (type === 'privacy_policy') { + return `<h1>개인정보 처리방침</h1> + +<h2>제1조 (목적)</h2> +<p>본 개인정보 처리방침은 eVCP(이하 "회사")가 개인정보 보호법 등 관련 법령에 따라 정보주체의 개인정보를 보호하고 이와 관련된 고충을 신속하고 원활하게 처리할 수 있도록 하기 위하여 다음과 같은 처리방침을 수립·공개합니다.</p> + +<h2>제2조 (개인정보의 수집 및 이용목적)</h2> +<p>회사는 다음의 목적을 위하여 개인정보를 처리합니다:</p> +<ul> + <li>회원 가입 및 관리</li> + <li>서비스 제공 및 계약 이행</li> + <li>고객 상담 및 불만 처리</li> +</ul> + +<h2>제3조 (개인정보의 수집항목)</h2> +<p><strong>필수항목:</strong></p> +<ul> + <li>이메일 주소</li> + <li>전화번호</li> + <li>회사명</li> +</ul> + +<h2>제4조 (개인정보의 보유 및 이용기간)</h2> +<p>회사는 법령에 따른 개인정보 보유·이용기간 또는 정보주체로부터 개인정보를 수집 시에 동의받은 개인정보 보유·이용기간 내에서 개인정보를 처리·보유합니다.</p> + +<h2>제5조 (정보주체의 권리)</h2> +<p>정보주체는 회사에 대해 언제든지 다음 각 호의 개인정보 보호 관련 권리를 행사할 수 있습니다:</p> +<ul> + <li>개인정보 처리현황 통지요구</li> + <li>개인정보 열람요구</li> + <li>개인정보 정정·삭제요구</li> + <li>개인정보 처리정지요구</li> +</ul>` + } else { + return `<h1>이용약관</h1> + +<h2>제1조 (목적)</h2> +<p>본 약관은 eVCP(이하 "회사")가 제공하는 서비스의 이용조건 및 절차, 회사와 회원 간의 권리, 의무 및 책임사항을 규정함을 목적으로 합니다.</p> + +<h2>제2조 (정의)</h2> +<ul> + <li><strong>"서비스"</strong>란 회사가 제공하는 모든 서비스를 의미합니다.</li> + <li><strong>"회원"</strong>이란 본 약관에 동의하고 회사와 서비스 이용계약을 체결한 자를 의미합니다.</li> + <li><strong>"업체"</strong>란 회사의 파트너로 등록된 법인 또는 개인사업자를 의미합니다.</li> +</ul> + +<h2>제3조 (약관의 효력 및 변경)</h2> +<p>본 약관은 서비스를 이용하고자 하는 모든 회원에 대하여 그 효력을 발생합니다.</p> + +<h2>제4조 (회원가입)</h2> +<p>회원가입은 신청자가 본 약관의 내용에 대하여 동의를 한 다음 회원가입신청을 하고 회사가 이러한 신청에 대하여 승낙함으로써 체결됩니다.</p> + +<h2>제5조 (서비스의 제공)</h2> +<p>회사는 회원에게 다음과 같은 서비스를 제공합니다:</p> +<ul> + <li>업체 등록 및 관리 서비스</li> + <li>문서 관리 서비스</li> + <li>견적 제출 서비스</li> +</ul>` + } + } + + const validateForm = () => { + const errors: string[] = [] + + if (!version.trim()) { + errors.push('버전을 입력해주세요.') + } else if (!/^\d+\.\d+$/.test(version.trim())) { + errors.push('버전은 "1.0" 형식으로 입력해주세요.') + } + + if (!content.trim()) { + errors.push('정책 내용을 입력해주세요.') + } else if (content.trim().length < 100) { + errors.push('정책 내용이 너무 짧습니다. (최소 100자)') + } + + // 버전 중복 체크 (현재 정책이 있는 경우) + if (currentPolicy && version === currentPolicy.version) { + errors.push('이미 존재하는 버전입니다. 다른 버전을 입력해주세요.') + } + + setValidationErrors(errors) + return errors.length === 0 + } + + const handleSave = () => { + if (validateForm()) { + onSave(policyType, version.trim(), content) + } + } + + const handlePreview = () => { + if (validateForm()) { + onPreview(policyType, content, version.trim()) + } + } + + return ( + <Card> + <CardHeader> + <CardTitle> + {currentPolicy ? '정책 편집' : '새 정책 생성'} - {policyLabels[policyType]} + </CardTitle> + <CardDescription> + {currentPolicy + ? `현재 버전 v${currentPolicy.version}을 기반으로 새 버전을 생성합니다.` + : `${policyLabels[policyType]}의 첫 번째 버전을 생성합니다.` + } + </CardDescription> + </CardHeader> + <CardContent className="space-y-6"> + {/* 경고 메시지 */} + <Alert> + <Info className="h-4 w-4" /> + <AlertDescription> + 새 버전을 저장하면 즉시 활성화되어 모든 사용자에게 적용됩니다. + 저장하기 전에 미리보기로 내용을 확인해주세요. + </AlertDescription> + </Alert> + + {/* 유효성 검사 오류 */} + {validationErrors.length > 0 && ( + <Alert variant="destructive"> + <AlertTriangle className="h-4 w-4" /> + <AlertDescription> + <ul className="list-disc list-inside space-y-1"> + {validationErrors.map((error, index) => ( + <li key={index}>{error}</li> + ))} + </ul> + </AlertDescription> + </Alert> + )} + + {/* 버전 입력 */} + <div className="space-y-2"> + <Label htmlFor="version">버전</Label> + <Input + id="version" + placeholder="예: 1.1" + value={version} + onChange={(e) => setVersion(e.target.value)} + disabled={isLoading} + className="w-32" + /> + <p className="text-xs text-muted-foreground"> + 형식: 주.부 (예: 1.0, 1.1, 2.0) + </p> + </div> + + <Separator /> + + {/* 정책 내용 편집기 */} + <div className="space-y-2"> + <Label>정책 내용</Label> + <div className="border rounded-md"> + <TiptapEditor + content={content} + setContent={setContent} + disabled={isLoading} + height="500px" + /> + </div> + <p className="text-xs text-muted-foreground"> + 리치 텍스트 편집기를 사용하여 정책 내용을 작성하세요. + 이미지, 표, 목록 등을 추가할 수 있습니다. + </p> + </div> + + {/* 액션 버튼들 */} + <div className="flex justify-between pt-4"> + <Button + variant="outline" + onClick={onCancel} + disabled={isLoading} + > + <X className="h-4 w-4 mr-2" /> + 취소 + </Button> + + <div className="flex gap-2"> + <Button + variant="outline" + onClick={handlePreview} + disabled={isLoading || !content.trim() || !version.trim()} + > + <Eye className="h-4 w-4 mr-2" /> + 미리보기 + </Button> + <Button + onClick={handleSave} + disabled={isLoading || !content.trim() || !version.trim()} + > + <Save className="h-4 w-4 mr-2" /> + {isLoading ? '저장 중...' : '저장'} + </Button> + </div> + </div> + </CardContent> + </Card> + ) +}
\ No newline at end of file |
