diff options
Diffstat (limited to 'lib/bidding/bidding-notice-editor.tsx')
| -rw-r--r-- | lib/bidding/bidding-notice-editor.tsx | 230 |
1 files changed, 230 insertions, 0 deletions
diff --git a/lib/bidding/bidding-notice-editor.tsx b/lib/bidding/bidding-notice-editor.tsx new file mode 100644 index 00000000..03b993b9 --- /dev/null +++ b/lib/bidding/bidding-notice-editor.tsx @@ -0,0 +1,230 @@ +'use client' + +import { useState, useTransition } from 'react' +import { useRouter } from 'next/navigation' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { useToast } from '@/hooks/use-toast' +import { Save, RefreshCw } from 'lucide-react' +import { BiddingNoticeTemplate } from '@/db/schema/bidding' +import { saveBiddingNoticeTemplate } from './service' +import TiptapEditor from '@/components/qna/tiptap-editor' + +interface BiddingNoticeEditorProps { + initialData: BiddingNoticeTemplate | null +} + +export function BiddingNoticeEditor({ initialData }: BiddingNoticeEditorProps) { + const [title, setTitle] = useState(initialData?.title || '표준 입찰공고문') + const [content, setContent] = useState(initialData?.content || getDefaultTemplate()) + const [isPending, startTransition] = useTransition() + const { toast } = useToast() + const router = useRouter() + + const handleSave = () => { + if (!title.trim()) { + toast({ + title: '오류', + description: '제목을 입력해주세요.', + variant: 'destructive', + }) + return + } + + if (!content.trim()) { + toast({ + title: '오류', + description: '내용을 입력해주세요.', + variant: 'destructive', + }) + return + } + + startTransition(async () => { + try { + await saveBiddingNoticeTemplate({ title, content }) + toast({ + title: '성공', + description: '입찰공고문 템플릿이 저장되었습니다.', + }) + router.refresh() + } catch (error) { + toast({ + title: '오류', + description: error instanceof Error ? error.message : '저장에 실패했습니다.', + variant: 'destructive', + }) + } + }) + } + + const handleReset = () => { + if (confirm('기본 템플릿으로 초기화하시겠습니까? 현재 내용은 삭제됩니다.')) { + setTitle('표준 입찰공고문') + setContent(getDefaultTemplate()) + toast({ + title: '초기화 완료', + description: '기본 템플릿으로 초기화되었습니다.', + }) + } + } + + return ( + <div className="space-y-6"> + {/* 제목 입력 */} + <div className="space-y-2"> + <Label htmlFor="title">템플릿 제목</Label> + <Input + id="title" + value={title} + onChange={(e) => setTitle(e.target.value)} + placeholder="입찰공고문 제목을 입력하세요" + disabled={isPending} + /> + </div> + + {/* 에디터 */} + <div className="space-y-2"> + <Label>공고문 내용</Label> + <div className="border rounded-lg"> + <TiptapEditor + content={content} + setContent={setContent} + disabled={isPending} + height="500px" + /> + </div> + </div> + + {/* 액션 버튼 */} + <div className="flex items-center gap-4 pt-4"> + <Button + onClick={handleSave} + disabled={isPending} + className="min-w-[120px]" + > + {isPending ? ( + <> + <RefreshCw className="w-4 h-4 mr-2 animate-spin" /> + 저장 중... + </> + ) : ( + <> + <Save className="w-4 h-4 mr-2" /> + 저장 + </> + )} + </Button> + + <Button + variant="outline" + onClick={handleReset} + disabled={isPending} + > + 기본 템플릿으로 초기화 + </Button> + + {initialData && ( + <div className="ml-auto text-sm text-muted-foreground"> + 마지막 업데이트: {new Date(initialData.updatedAt).toLocaleString('ko-KR')} + </div> + )} + </div> + + {/* 미리보기 힌트 */} + <div className="bg-muted/50 p-4 rounded-lg"> + <p className="text-sm text-muted-foreground"> + <strong>💡 사용 팁:</strong> + 이 템플릿은 실제 입찰 공고 작성 시 기본값으로 사용됩니다. + 회사 정보, 표준 조건, 서식 등을 미리 작성해두면 편리합니다. + </p> + </div> + </div> + ) +} + +// 기본 템플릿 함수 +function getDefaultTemplate(): string { + return ` +<h1>입찰공고</h1> + +<h2>1. 입찰 개요</h2> +<ul> + <li><strong>공고명:</strong> [입찰 공고명을 입력하세요]</li> + <li><strong>입찰방식:</strong> [일반경쟁입찰/제한경쟁입찰]</li> + <li><strong>입찰공고번호:</strong> [공고번호]</li> + <li><strong>공고일자:</strong> [YYYY년 MM월 DD일]</li> +</ul> + +<h2>2. 입찰 참가자격</h2> +<ul> + <li>관련 업종의 사업자등록증을 보유한 업체</li> + <li>부가가치세법에 의한 사업자등록증을 보유한 업체</li> + <li>기타 관련 법령에 따른 자격 요건을 갖춘 업체</li> +</ul> + +<h2>3. 입찰 일정</h2> +<table> + <thead> + <tr> + <th>구분</th> + <th>일시</th> + <th>장소</th> + </tr> + </thead> + <tbody> + <tr> + <td>입찰 공고</td> + <td>[YYYY.MM.DD]</td> + <td>-</td> + </tr> + <tr> + <td>현장설명</td> + <td>[YYYY.MM.DD HH:MM]</td> + <td>[현장 주소]</td> + </tr> + <tr> + <td>입찰서 접수</td> + <td>[YYYY.MM.DD HH:MM까지]</td> + <td>[접수 장소]</td> + </tr> + <tr> + <td>개찰</td> + <td>[YYYY.MM.DD HH:MM]</td> + <td>[개찰 장소]</td> + </tr> + </tbody> +</table> + +<h2>4. 입찰 대상</h2> +<ul> + <li><strong>사업명:</strong> [사업명을 입력하세요]</li> + <li><strong>사업내용:</strong> [상세 사업내용]</li> + <li><strong>사업기간:</strong> [계약일로부터 OO일 이내]</li> + <li><strong>사업장소:</strong> [사업 수행 장소]</li> +</ul> + +<h2>5. 제출 서류</h2> +<ul> + <li>입찰서 및 투찰서</li> + <li>사업자등록증 사본</li> + <li>법인등기부등본 (법인의 경우)</li> + <li>업체현황서</li> + <li>기타 입찰 참가자격 증명서류</li> +</ul> + +<h2>6. 기타 사항</h2> +<ul> + <li>본 입찰공고에 명시되지 않은 사항은 관련 법령에 따릅니다.</li> + <li>기타 문의사항은 아래 연락처로 문의하시기 바랍니다.</li> +</ul> + +<blockquote> +<p><strong>문의처:</strong><br> +담당자: [담당자명]<br> +전화: [전화번호]<br> +이메일: [이메일주소]</p> +</blockquote> + `.trim() +}
\ No newline at end of file |
