feat: 신규 페이지 구현 및 HR/설정 기능 개선
신규 페이지: - 회계관리: 거래처, 예상비용, 청구서, 발주서 - 게시판: 공지사항, 자료실, 커뮤니티 - 고객센터: 문의/FAQ - 설정: 계정, 알림, 출퇴근, 팝업, 구독, 결제내역 - 리포트 (차트 시각화) - 개발자 테스트 URL 페이지 기능 개선: - HR 직원관리/휴가관리/카드관리 강화 - IntegratedListTemplateV2 확장 - AuthenticatedLayout 패딩 표준화 - 로그인 페이지 UI 개선 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { X } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from '@/components/ui/alert-dialog';
|
||||
|
||||
interface AddCompanyDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export function AddCompanyDialog({ open, onOpenChange }: AddCompanyDialogProps) {
|
||||
const [businessNumber, setBusinessNumber] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
// Alert 상태
|
||||
const [alertOpen, setAlertOpen] = useState(false);
|
||||
const [alertMessage, setAlertMessage] = useState('');
|
||||
|
||||
// 숫자만 입력 가능 (10자리 제한)
|
||||
const handleBusinessNumberChange = (value: string) => {
|
||||
const numbersOnly = value.replace(/[^0-9]/g, '');
|
||||
if (numbersOnly.length <= 10) {
|
||||
setBusinessNumber(numbersOnly);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setBusinessNumber('');
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
const handleNext = async () => {
|
||||
if (businessNumber.length !== 10) {
|
||||
setAlertMessage('사업자등록번호는 10자리를 입력해주세요.');
|
||||
setAlertOpen(true);
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
// TODO: 바로빌 API 연동
|
||||
// 1) 사업자등록번호 조회
|
||||
// 2) 휴폐업 상태 확인
|
||||
// 3) 기존 등록 여부 확인
|
||||
|
||||
// Mock 로직 - 실제로는 API 응답에 따라 처리
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
// 케이스별 처리
|
||||
const mockCase = Math.floor(Math.random() * 3);
|
||||
|
||||
if (mockCase === 0) {
|
||||
// 휴폐업 상태
|
||||
setAlertMessage('휴폐업 상태인 사업자입니다.');
|
||||
} else if (mockCase === 1) {
|
||||
// 이미 등록된 번호
|
||||
setAlertMessage('등록된 사업자등록번호 입니다.');
|
||||
} else {
|
||||
// 신규 등록 가능 - 매니저에게 알림
|
||||
setAlertMessage('매니저에게 회사 추가 신청 알림을 발송했습니다. 연락을 기다려주세요.');
|
||||
setBusinessNumber('');
|
||||
onOpenChange(false);
|
||||
}
|
||||
|
||||
setAlertOpen(true);
|
||||
} catch (error) {
|
||||
setAlertMessage('사업자등록번호 조회 중 오류가 발생했습니다.');
|
||||
setAlertOpen(true);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-[400px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>회사 추가</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="businessNumber">사업자등록번호</Label>
|
||||
<Input
|
||||
id="businessNumber"
|
||||
value={businessNumber}
|
||||
onChange={(e) => handleBusinessNumberChange(e.target.value)}
|
||||
placeholder="숫자 10자리 입력"
|
||||
maxLength={10}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
숫자만 가능, 10자리
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleCancel}
|
||||
disabled={isLoading}
|
||||
>
|
||||
취소
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleNext}
|
||||
disabled={isLoading || businessNumber.length !== 10}
|
||||
>
|
||||
{isLoading ? '조회 중...' : '다음'}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Alert 다이얼로그 */}
|
||||
<AlertDialog open={alertOpen} onOpenChange={setAlertOpen}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>알림</AlertDialogTitle>
|
||||
<AlertDialogDescription>{alertMessage}</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogAction>확인</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
488
src/components/settings/CompanyInfoManagement/index.tsx
Normal file
488
src/components/settings/CompanyInfoManagement/index.tsx
Normal file
@@ -0,0 +1,488 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useRef, useCallback } from 'react';
|
||||
import { Building2, Plus, Save, Upload, X, Search } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { PageLayout } from '@/components/organisms/PageLayout';
|
||||
import { PageHeader } from '@/components/organisms/PageHeader';
|
||||
import { AddCompanyDialog } from './AddCompanyDialog';
|
||||
import type { CompanyFormData } from './types';
|
||||
import { INITIAL_FORM_DATA, PAYMENT_DAY_OPTIONS } from './types';
|
||||
|
||||
// Mock 데이터 (실제로는 API에서 가져옴)
|
||||
const MOCK_COMPANY_DATA: CompanyFormData = {
|
||||
companyLogo: undefined,
|
||||
companyName: '주식회사 샘플',
|
||||
representativeName: '홍길동',
|
||||
businessType: '서비스업',
|
||||
businessCategory: 'IT',
|
||||
zipCode: '01234',
|
||||
address: '서울시 강남구 테헤란로',
|
||||
addressDetail: '123번지 샘플빌딩 5층',
|
||||
email: 'sample@company.com',
|
||||
taxInvoiceEmail: 'tax@company.com',
|
||||
managerName: '김담당',
|
||||
managerPhone: '010-1234-5678',
|
||||
businessLicense: 'abc.pdf',
|
||||
businessNumber: '123-12-12345',
|
||||
paymentBank: '신한은행',
|
||||
paymentAccount: '123-1231-23-123',
|
||||
paymentAccountHolder: '홍길동',
|
||||
paymentDay: '25',
|
||||
};
|
||||
|
||||
export function CompanyInfoManagement() {
|
||||
const [isEditMode, setIsEditMode] = useState(false);
|
||||
const [showAddDialog, setShowAddDialog] = useState(false);
|
||||
const [formData, setFormData] = useState<CompanyFormData>(MOCK_COMPANY_DATA);
|
||||
|
||||
// 파일 input refs
|
||||
const logoInputRef = useRef<HTMLInputElement>(null);
|
||||
const licenseInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
// 로고 파일명
|
||||
const [logoFileName, setLogoFileName] = useState<string>('');
|
||||
// 사업자등록증 파일명
|
||||
const [licenseFileName, setLicenseFileName] = useState<string>(
|
||||
typeof MOCK_COMPANY_DATA.businessLicense === 'string'
|
||||
? MOCK_COMPANY_DATA.businessLicense
|
||||
: ''
|
||||
);
|
||||
|
||||
const handleChange = useCallback((field: keyof CompanyFormData, value: string) => {
|
||||
setFormData(prev => ({ ...prev, [field]: value }));
|
||||
}, []);
|
||||
|
||||
const handleLogoUpload = () => {
|
||||
logoInputRef.current?.click();
|
||||
};
|
||||
|
||||
const handleLogoChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) {
|
||||
// 파일 크기 체크 (10MB)
|
||||
if (file.size > 10 * 1024 * 1024) {
|
||||
alert('파일 크기는 10MB 이하여야 합니다.');
|
||||
return;
|
||||
}
|
||||
// 파일 타입 체크
|
||||
if (!['image/png', 'image/jpeg', 'image/gif'].includes(file.type)) {
|
||||
alert('PNG, JPEG, GIF 파일만 업로드 가능합니다.');
|
||||
return;
|
||||
}
|
||||
setFormData(prev => ({ ...prev, companyLogo: file }));
|
||||
setLogoFileName(file.name);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveLogo = () => {
|
||||
setFormData(prev => ({ ...prev, companyLogo: undefined }));
|
||||
setLogoFileName('');
|
||||
if (logoInputRef.current) {
|
||||
logoInputRef.current.value = '';
|
||||
}
|
||||
};
|
||||
|
||||
const handleLicenseUpload = () => {
|
||||
licenseInputRef.current?.click();
|
||||
};
|
||||
|
||||
const handleLicenseChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) {
|
||||
setFormData(prev => ({ ...prev, businessLicense: file }));
|
||||
setLicenseFileName(file.name);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveLicense = () => {
|
||||
setFormData(prev => ({ ...prev, businessLicense: undefined }));
|
||||
setLicenseFileName('');
|
||||
if (licenseInputRef.current) {
|
||||
licenseInputRef.current.value = '';
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddressSearch = () => {
|
||||
// TODO: 다음 주소 API 연동
|
||||
console.log('주소 검색');
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
// TODO: API 연동
|
||||
console.log('저장:', formData);
|
||||
setIsEditMode(false);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setFormData(MOCK_COMPANY_DATA);
|
||||
setIsEditMode(false);
|
||||
};
|
||||
|
||||
// 헤더 액션 버튼
|
||||
const headerActions = (
|
||||
<div className="flex items-center gap-2">
|
||||
<Button onClick={() => setShowAddDialog(true)}>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
회사 추가
|
||||
</Button>
|
||||
{!isEditMode && (
|
||||
<Button variant="outline" onClick={() => setIsEditMode(true)}>
|
||||
수정
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<PageLayout>
|
||||
<PageHeader
|
||||
title="회사정보"
|
||||
description="회사 정보를 관리합니다"
|
||||
icon={Building2}
|
||||
actions={headerActions}
|
||||
/>
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* 회사 정보 섹션 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">회사 정보</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
{/* 회사 로고 */}
|
||||
<div className="space-y-2">
|
||||
<Label>회사 로고</Label>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="w-[200px] h-[67px] border rounded-lg flex items-center justify-center bg-muted/50 overflow-hidden">
|
||||
{logoFileName ? (
|
||||
<span className="text-sm text-muted-foreground truncate px-2">
|
||||
{logoFileName}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-sm text-muted-foreground">IMG</span>
|
||||
)}
|
||||
</div>
|
||||
{isEditMode && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleLogoUpload}
|
||||
>
|
||||
<Upload className="w-4 h-4 mr-2" />
|
||||
업로드
|
||||
</Button>
|
||||
{logoFileName && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleRemoveLogo}
|
||||
>
|
||||
<X className="w-4 h-4 mr-2" />
|
||||
삭제
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
750 X 250px, 10MB 이하의 PNG, JPEG, GIF
|
||||
</p>
|
||||
<input
|
||||
ref={logoInputRef}
|
||||
type="file"
|
||||
accept="image/png,image/jpeg,image/gif"
|
||||
className="hidden"
|
||||
onChange={handleLogoChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 회사명 / 대표자명 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="companyName">회사명</Label>
|
||||
<Input
|
||||
id="companyName"
|
||||
value={formData.companyName}
|
||||
onChange={(e) => handleChange('companyName', e.target.value)}
|
||||
placeholder="회사명"
|
||||
disabled={!isEditMode}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="representativeName">대표자명</Label>
|
||||
<Input
|
||||
id="representativeName"
|
||||
value={formData.representativeName}
|
||||
onChange={(e) => handleChange('representativeName', e.target.value)}
|
||||
placeholder="대표자명"
|
||||
disabled={!isEditMode}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 업태 / 업종 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="businessType">업태</Label>
|
||||
<Input
|
||||
id="businessType"
|
||||
value={formData.businessType}
|
||||
onChange={(e) => handleChange('businessType', e.target.value)}
|
||||
placeholder="업태명"
|
||||
disabled={!isEditMode}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="businessCategory">업종</Label>
|
||||
<Input
|
||||
id="businessCategory"
|
||||
value={formData.businessCategory}
|
||||
onChange={(e) => handleChange('businessCategory', e.target.value)}
|
||||
placeholder="업종명"
|
||||
disabled={!isEditMode}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 주소 */}
|
||||
<div className="space-y-2">
|
||||
<Label>주소</Label>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={handleAddressSearch}
|
||||
disabled={!isEditMode}
|
||||
>
|
||||
우편번호 찾기
|
||||
</Button>
|
||||
<Input
|
||||
value={formData.zipCode ? `${formData.zipCode} ${formData.address}` : ''}
|
||||
placeholder="주소명"
|
||||
disabled
|
||||
className="flex-1"
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
value={formData.addressDetail}
|
||||
onChange={(e) => handleChange('addressDetail', e.target.value)}
|
||||
placeholder="상세주소"
|
||||
disabled={!isEditMode}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 이메일 / 세금계산서 이메일 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="email">이메일 (아이디)</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
value={formData.email}
|
||||
onChange={(e) => handleChange('email', e.target.value)}
|
||||
placeholder="abc@email.com"
|
||||
disabled={!isEditMode}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="taxInvoiceEmail">세금계산서 이메일</Label>
|
||||
<Input
|
||||
id="taxInvoiceEmail"
|
||||
type="email"
|
||||
value={formData.taxInvoiceEmail}
|
||||
onChange={(e) => handleChange('taxInvoiceEmail', e.target.value)}
|
||||
placeholder="abc@email.com"
|
||||
disabled={!isEditMode}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 담당자명 / 담당자 연락처 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="managerName">담당자명</Label>
|
||||
<Input
|
||||
id="managerName"
|
||||
value={formData.managerName}
|
||||
onChange={(e) => handleChange('managerName', e.target.value)}
|
||||
placeholder="담당자명"
|
||||
disabled={!isEditMode}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="managerPhone">담당자 연락처</Label>
|
||||
<Input
|
||||
id="managerPhone"
|
||||
value={formData.managerPhone}
|
||||
onChange={(e) => handleChange('managerPhone', e.target.value)}
|
||||
placeholder="010-1234-1234"
|
||||
disabled={!isEditMode}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 사업자등록증 / 사업자등록번호 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="space-y-2">
|
||||
<Label>사업자등록증</Label>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={handleLicenseUpload}
|
||||
disabled={!isEditMode}
|
||||
>
|
||||
찾기
|
||||
</Button>
|
||||
{licenseFileName && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm">{licenseFileName}</span>
|
||||
{isEditMode && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleRemoveLicense}
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<input
|
||||
ref={licenseInputRef}
|
||||
type="file"
|
||||
accept=".pdf,.jpg,.jpeg,.png"
|
||||
className="hidden"
|
||||
onChange={handleLicenseChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="businessNumber">사업자등록번호</Label>
|
||||
<Input
|
||||
id="businessNumber"
|
||||
value={formData.businessNumber}
|
||||
onChange={(e) => handleChange('businessNumber', e.target.value)}
|
||||
placeholder="123-12-12345"
|
||||
disabled={!isEditMode}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 결제 계좌 정보 섹션 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">결제 계좌 정보</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
{/* 결제 은행 / 계좌 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="paymentBank">결제 은행</Label>
|
||||
<Input
|
||||
id="paymentBank"
|
||||
value={formData.paymentBank}
|
||||
onChange={(e) => handleChange('paymentBank', e.target.value)}
|
||||
placeholder="은행명"
|
||||
disabled={!isEditMode}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="paymentAccount">계좌</Label>
|
||||
<Input
|
||||
id="paymentAccount"
|
||||
value={formData.paymentAccount}
|
||||
onChange={(e) => handleChange('paymentAccount', e.target.value)}
|
||||
placeholder="123-1231-23-123"
|
||||
disabled={!isEditMode}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 예금주 / 결제일 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="paymentAccountHolder">예금주</Label>
|
||||
<Input
|
||||
id="paymentAccountHolder"
|
||||
value={formData.paymentAccountHolder}
|
||||
onChange={(e) => handleChange('paymentAccountHolder', e.target.value)}
|
||||
placeholder="예금주명"
|
||||
disabled={!isEditMode}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="paymentDay">결제일</Label>
|
||||
{isEditMode ? (
|
||||
<Select
|
||||
value={formData.paymentDay}
|
||||
onValueChange={(value) => handleChange('paymentDay', value)}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="결제일 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{PAYMENT_DAY_OPTIONS.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
) : (
|
||||
<Input
|
||||
id="paymentDay"
|
||||
value={
|
||||
PAYMENT_DAY_OPTIONS.find(o => o.value === formData.paymentDay)?.label ||
|
||||
formData.paymentDay
|
||||
}
|
||||
disabled
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 수정 모드 버튼 */}
|
||||
{isEditMode && (
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<Button variant="outline" onClick={handleCancel}>
|
||||
<X className="w-4 h-4 mr-2" />
|
||||
취소
|
||||
</Button>
|
||||
<Button onClick={handleSave}>
|
||||
<Save className="w-4 h-4 mr-2" />
|
||||
저장
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 회사 추가 다이얼로그 */}
|
||||
<AddCompanyDialog
|
||||
open={showAddDialog}
|
||||
onOpenChange={setShowAddDialog}
|
||||
/>
|
||||
</PageLayout>
|
||||
);
|
||||
}
|
||||
84
src/components/settings/CompanyInfoManagement/types.ts
Normal file
84
src/components/settings/CompanyInfoManagement/types.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
// ===== 회사 정보 타입 =====
|
||||
export interface CompanyInfo {
|
||||
id: string;
|
||||
// 회사 정보
|
||||
companyLogo?: string;
|
||||
companyName: string;
|
||||
representativeName: string;
|
||||
businessType: string; // 업태
|
||||
businessCategory: string; // 업종
|
||||
// 주소
|
||||
zipCode: string;
|
||||
address: string;
|
||||
addressDetail: string;
|
||||
// 연락처
|
||||
email: string;
|
||||
taxInvoiceEmail: string;
|
||||
managerName: string;
|
||||
managerPhone: string;
|
||||
// 사업자 정보
|
||||
businessLicense?: string; // 사업자등록증 파일
|
||||
businessNumber: string; // 사업자등록번호
|
||||
// 결제 계좌 정보
|
||||
paymentBank: string;
|
||||
paymentAccount: string;
|
||||
paymentAccountHolder: string;
|
||||
paymentDay: string;
|
||||
// 메타
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface CompanyFormData {
|
||||
companyLogo?: File | string;
|
||||
companyName: string;
|
||||
representativeName: string;
|
||||
businessType: string;
|
||||
businessCategory: string;
|
||||
zipCode: string;
|
||||
address: string;
|
||||
addressDetail: string;
|
||||
email: string;
|
||||
taxInvoiceEmail: string;
|
||||
managerName: string;
|
||||
managerPhone: string;
|
||||
businessLicense?: File | string;
|
||||
businessNumber: string;
|
||||
paymentBank: string;
|
||||
paymentAccount: string;
|
||||
paymentAccountHolder: string;
|
||||
paymentDay: string;
|
||||
}
|
||||
|
||||
// ===== 초기 폼 데이터 =====
|
||||
export const INITIAL_FORM_DATA: CompanyFormData = {
|
||||
companyLogo: undefined,
|
||||
companyName: '',
|
||||
representativeName: '',
|
||||
businessType: '',
|
||||
businessCategory: '',
|
||||
zipCode: '',
|
||||
address: '',
|
||||
addressDetail: '',
|
||||
email: '',
|
||||
taxInvoiceEmail: '',
|
||||
managerName: '',
|
||||
managerPhone: '',
|
||||
businessLicense: undefined,
|
||||
businessNumber: '',
|
||||
paymentBank: '',
|
||||
paymentAccount: '',
|
||||
paymentAccountHolder: '',
|
||||
paymentDay: '',
|
||||
};
|
||||
|
||||
// ===== 결제일 옵션 =====
|
||||
export const PAYMENT_DAY_OPTIONS = [
|
||||
{ value: '1', label: '매월 1일' },
|
||||
{ value: '5', label: '매월 5일' },
|
||||
{ value: '10', label: '매월 10일' },
|
||||
{ value: '15', label: '매월 15일' },
|
||||
{ value: '20', label: '매월 20일' },
|
||||
{ value: '25', label: '매월 25일' },
|
||||
{ value: 'last', label: '매월 말일' },
|
||||
];
|
||||
Reference in New Issue
Block a user