- 등록(?mode=new), 상세(?mode=view), 수정(?mode=edit) URL 패턴 일괄 적용
- 중복 패턴 제거: /edit?mode=edit → ?mode=edit (16개 파일)
- 제목 일관성: {기능} 등록/상세/수정 패턴 적용
- 검수 체크리스트 문서 추가 (79개 페이지)
- UniversalListPage, IntegratedDetailTemplate 공통 컴포넌트 개선
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
271 lines
9.6 KiB
TypeScript
271 lines
9.6 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import { useRouter } from 'next/navigation';
|
|
import { PageLayout } from '@/components/organisms/PageLayout';
|
|
import { PageHeader } from '@/components/organisms/PageHeader';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Label } from '@/components/ui/label';
|
|
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from '@/components/ui/select';
|
|
import { Checkbox } from '@/components/ui/checkbox';
|
|
import { ClipboardList, ArrowLeft, Save } from 'lucide-react';
|
|
import type { Board, BoardFormData, BoardTarget, BoardStatus } from './types';
|
|
import { BOARD_TARGETS, BOARD_STATUS_LABELS } from './types';
|
|
|
|
// TODO: API에서 부서 목록 가져오기
|
|
const MOCK_DEPARTMENTS = [
|
|
{ id: 1, name: '경영지원팀' },
|
|
{ id: 2, name: '영업팀' },
|
|
{ id: 3, name: '개발팀' },
|
|
{ id: 4, name: '디자인팀' },
|
|
{ id: 5, name: '마케팅팀' },
|
|
];
|
|
|
|
// TODO: API에서 권한 목록 가져오기
|
|
const MOCK_PERMISSIONS = [
|
|
{ code: 'admin', name: '관리자' },
|
|
{ code: 'manager', name: '매니저' },
|
|
{ code: 'staff', name: '직원' },
|
|
{ code: 'guest', name: '게스트' },
|
|
];
|
|
|
|
interface BoardFormProps {
|
|
mode: 'create' | 'edit';
|
|
board?: Board;
|
|
onSubmit: (data: BoardFormData) => void;
|
|
}
|
|
|
|
// 날짜/시간 포맷
|
|
const formatDateTime = (dateString: string): string => {
|
|
const date = new Date(dateString);
|
|
const year = date.getFullYear();
|
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
const day = String(date.getDate()).padStart(2, '0');
|
|
const hours = String(date.getHours()).padStart(2, '0');
|
|
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
|
};
|
|
|
|
// 현재 날짜/시간
|
|
const getCurrentDateTime = (): string => {
|
|
return formatDateTime(new Date().toISOString());
|
|
};
|
|
|
|
export function BoardForm({ mode, board, onSubmit }: BoardFormProps) {
|
|
const router = useRouter();
|
|
const [formData, setFormData] = useState<BoardFormData>({
|
|
target: 'all',
|
|
targetName: '',
|
|
permissions: [],
|
|
boardName: '',
|
|
status: 'active',
|
|
});
|
|
|
|
// 수정 모드일 때 기존 데이터 로드
|
|
useEffect(() => {
|
|
if (mode === 'edit' && board) {
|
|
setFormData({
|
|
target: board.target,
|
|
targetName: board.targetName || '',
|
|
permissions: board.permissions || [],
|
|
boardName: board.boardName,
|
|
status: board.status,
|
|
});
|
|
}
|
|
}, [mode, board]);
|
|
|
|
const handleBack = () => {
|
|
if (mode === 'edit' && board) {
|
|
router.push(`/ko/board/board-management/${board.id}?mode=view`);
|
|
} else {
|
|
router.push('/ko/board/board-management');
|
|
}
|
|
};
|
|
|
|
const handleSubmit = (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
onSubmit(formData);
|
|
};
|
|
|
|
const handleTargetChange = (value: BoardTarget) => {
|
|
setFormData(prev => ({
|
|
...prev,
|
|
target: value,
|
|
targetName: value === 'department' ? prev.targetName : '',
|
|
permissions: value === 'permission' ? prev.permissions : [],
|
|
}));
|
|
};
|
|
|
|
// 권한 체크박스 핸들러
|
|
const handlePermissionChange = (code: string, checked: boolean) => {
|
|
setFormData(prev => ({
|
|
...prev,
|
|
permissions: checked
|
|
? [...(prev.permissions || []), code]
|
|
: (prev.permissions || []).filter(p => p !== code),
|
|
}));
|
|
};
|
|
|
|
// 작성자 (현재 로그인한 사용자 - mock)
|
|
const currentUser = '홍길동';
|
|
|
|
// 등록일시
|
|
const registeredAt = mode === 'edit' && board ? formatDateTime(board.createdAt) : getCurrentDateTime();
|
|
|
|
return (
|
|
<PageLayout>
|
|
<PageHeader
|
|
title={mode === 'create' ? '게시판 등록' : '게시판 수정'}
|
|
description={mode === 'create' ? '새 게시판을 등록합니다' : '게시판 정보를 수정합니다'}
|
|
icon={ClipboardList}
|
|
/>
|
|
|
|
<form onSubmit={handleSubmit} className="space-y-6">
|
|
{/* 게시판 정보 */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="text-base">게시판 정보 *</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
{/* 대상 */}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="target">대상</Label>
|
|
<div className="flex gap-2">
|
|
<Select
|
|
value={formData.target}
|
|
onValueChange={(value) => handleTargetChange(value as BoardTarget)}
|
|
>
|
|
<SelectTrigger id="target" className="w-[120px]">
|
|
<SelectValue placeholder="대상 선택" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{BOARD_TARGETS.map((target) => (
|
|
<SelectItem key={target.value} value={target.value}>
|
|
{target.label}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
{formData.target === 'department' && (
|
|
<Select
|
|
value={formData.targetName}
|
|
onValueChange={(value) => setFormData(prev => ({ ...prev, targetName: value }))}
|
|
>
|
|
<SelectTrigger className="flex-1">
|
|
<SelectValue placeholder="부서 선택" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{MOCK_DEPARTMENTS.map((dept) => (
|
|
<SelectItem key={dept.id} value={dept.name}>
|
|
{dept.name}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
)}
|
|
{formData.target === 'permission' && (
|
|
<div className="flex-1 flex flex-wrap gap-4 items-center border rounded-md px-3 py-2">
|
|
{MOCK_PERMISSIONS.map((perm) => (
|
|
<div key={perm.code} className="flex items-center space-x-2">
|
|
<Checkbox
|
|
id={`perm-${perm.code}`}
|
|
checked={(formData.permissions || []).includes(perm.code)}
|
|
onCheckedChange={(checked) => handlePermissionChange(perm.code, checked as boolean)}
|
|
/>
|
|
<Label
|
|
htmlFor={`perm-${perm.code}`}
|
|
className="font-normal cursor-pointer text-sm"
|
|
>
|
|
{perm.name}
|
|
</Label>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* 작성자 */}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="author">작성자</Label>
|
|
<Input
|
|
id="author"
|
|
value={mode === 'edit' && board ? board.authorName : currentUser}
|
|
disabled
|
|
className="bg-muted"
|
|
/>
|
|
</div>
|
|
|
|
{/* 게시판명 */}
|
|
<div className="space-y-2 md:col-span-2">
|
|
<Label htmlFor="boardName">게시판명</Label>
|
|
<Input
|
|
id="boardName"
|
|
value={formData.boardName}
|
|
onChange={(e) => setFormData(prev => ({ ...prev, boardName: e.target.value }))}
|
|
placeholder="게시판명을 입력해주세요"
|
|
/>
|
|
</div>
|
|
|
|
{/* 상태 */}
|
|
<div className="space-y-2">
|
|
<Label>상태</Label>
|
|
<RadioGroup
|
|
value={formData.status}
|
|
onValueChange={(value) => setFormData(prev => ({ ...prev, status: value as BoardStatus }))}
|
|
className="flex items-center gap-4"
|
|
>
|
|
<div className="flex items-center space-x-2">
|
|
<RadioGroupItem value="inactive" id="inactive" />
|
|
<Label htmlFor="inactive" className="font-normal cursor-pointer">
|
|
{BOARD_STATUS_LABELS.inactive}
|
|
</Label>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<RadioGroupItem value="active" id="active" />
|
|
<Label htmlFor="active" className="font-normal cursor-pointer">
|
|
{BOARD_STATUS_LABELS.active}
|
|
</Label>
|
|
</div>
|
|
</RadioGroup>
|
|
</div>
|
|
|
|
{/* 등록일시 */}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="registeredAt">등록일시</Label>
|
|
<Input
|
|
id="registeredAt"
|
|
value={registeredAt}
|
|
disabled
|
|
className="bg-muted"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* 버튼 영역 */}
|
|
<div className="flex items-center justify-between">
|
|
<Button type="button" variant="outline" onClick={handleBack}>
|
|
<ArrowLeft className="w-4 h-4 mr-2" />
|
|
취소
|
|
</Button>
|
|
<Button type="submit">
|
|
<Save className="w-4 h-4 mr-2" />
|
|
{mode === 'create' ? '등록' : '저장'}
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
</PageLayout>
|
|
);
|
|
} |