feat(WEB): 회계/설정/카드 관리 페이지 대규모 기능 추가 및 리팩토링
- 일반전표입력, 상품권관리, 세금계산서 발행/조회 신규 페이지 추가 - 바로빌 연동 설정 페이지 추가 - 카드관리/계좌관리 리스트 UniversalListPage 공통 구조로 전환 - 카드거래조회/은행거래조회 리팩토링 (모달 분리, 액션 확장) - 계좌 상세 폼(AccountDetailForm) 신규 구현 - 카드 상세(CardDetail) 신규 구현 + CardNumberInput 적용 - DateRangeSelector, StatCards, IntegratedListTemplateV2 공통 컴포넌트 개선 - 레거시 파일 정리 (CardManagementUnified, cardConfig, _legacy 등) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,13 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { use } from 'react';
|
||||
import CardTransactionDetailClient from '@/components/accounting/CardTransactionInquiry/CardTransactionDetailClient';
|
||||
|
||||
interface PageProps {
|
||||
params: Promise<{ id: string }>;
|
||||
}
|
||||
|
||||
export default function CardTransactionEditPage({ params }: PageProps) {
|
||||
const { id } = use(params);
|
||||
return <CardTransactionDetailClient transactionId={id} initialMode="edit" />;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { use } from 'react';
|
||||
import CardTransactionDetailClient from '@/components/accounting/CardTransactionInquiry/CardTransactionDetailClient';
|
||||
|
||||
interface PageProps {
|
||||
params: Promise<{ id: string }>;
|
||||
}
|
||||
|
||||
export default function CardTransactionDetailPage({ params }: PageProps) {
|
||||
const { id } = use(params);
|
||||
return <CardTransactionDetailClient transactionId={id} initialMode="view" />;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import CardTransactionDetailClient from '@/components/accounting/CardTransactionInquiry/CardTransactionDetailClient';
|
||||
|
||||
export default function CardTransactionNewPage() {
|
||||
return <CardTransactionDetailClient initialMode="create" />;
|
||||
}
|
||||
@@ -1,16 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { CardTransactionInquiry } from '@/components/accounting/CardTransactionInquiry';
|
||||
import CardTransactionDetailClient from '@/components/accounting/CardTransactionInquiry/CardTransactionDetailClient';
|
||||
|
||||
export default function CardTransactionsPage() {
|
||||
const searchParams = useSearchParams();
|
||||
const mode = searchParams.get('mode');
|
||||
|
||||
if (mode === 'new') {
|
||||
return <CardTransactionDetailClient initialMode="create" />;
|
||||
}
|
||||
|
||||
return <CardTransactionInquiry />;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { GeneralJournalEntry } from '@/components/accounting/GeneralJournalEntry';
|
||||
|
||||
export default function GeneralJournalEntryPage() {
|
||||
return <GeneralJournalEntry />;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { GiftCertificateManagement } from '@/components/accounting/GiftCertificateManagement';
|
||||
import { GiftCertificateDetail } from '@/components/accounting/GiftCertificateManagement/GiftCertificateDetail';
|
||||
import { getGiftCertificateById } from '@/components/accounting/GiftCertificateManagement/actions';
|
||||
import type { GiftCertificateFormData } from '@/components/accounting/GiftCertificateManagement/types';
|
||||
|
||||
export default function GiftCertificatesPage() {
|
||||
const searchParams = useSearchParams();
|
||||
const mode = searchParams.get('mode');
|
||||
const id = searchParams.get('id');
|
||||
|
||||
const [editData, setEditData] = useState<GiftCertificateFormData | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (mode === 'edit' && id) {
|
||||
setIsLoading(true);
|
||||
getGiftCertificateById(id)
|
||||
.then((result) => {
|
||||
if (result.success && result.data) {
|
||||
setEditData(result.data);
|
||||
}
|
||||
})
|
||||
.finally(() => setIsLoading(false));
|
||||
}
|
||||
}, [mode, id]);
|
||||
|
||||
if (mode === 'new') {
|
||||
return <GiftCertificateDetail mode="new" />;
|
||||
}
|
||||
|
||||
if (mode === 'edit' && id) {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-[400px]">
|
||||
<div className="text-muted-foreground">로딩 중...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<GiftCertificateDetail
|
||||
mode="edit"
|
||||
id={id}
|
||||
initialData={editData ?? undefined}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// 목록 - 컴포넌트가 자체 데이터 로딩 처리
|
||||
return <GiftCertificateManagement />;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { TaxInvoiceIssuancePage } from '@/components/accounting/TaxInvoiceIssuance';
|
||||
import { TaxInvoiceDetail } from '@/components/accounting/TaxInvoiceIssuance/TaxInvoiceDetail';
|
||||
import {
|
||||
getTaxInvoices,
|
||||
getSupplierSettings,
|
||||
getTaxInvoiceById,
|
||||
} from '@/components/accounting/TaxInvoiceIssuance/actions';
|
||||
import type { TaxInvoiceRecord, TaxInvoiceFormData, SupplierSettings } from '@/components/accounting/TaxInvoiceIssuance/types';
|
||||
import { createEmptyBusinessEntity } from '@/components/accounting/TaxInvoiceIssuance/types';
|
||||
|
||||
type TaxInvoiceDetailData = TaxInvoiceFormData & {
|
||||
id: string;
|
||||
invoiceNumber: string;
|
||||
status: TaxInvoiceRecord['status'];
|
||||
};
|
||||
|
||||
export default function TaxInvoiceIssuanceRoute() {
|
||||
const searchParams = useSearchParams();
|
||||
const mode = searchParams.get('mode');
|
||||
const id = searchParams.get('id');
|
||||
|
||||
const [data, setData] = useState<TaxInvoiceRecord[]>([]);
|
||||
const [supplierSettings, setSupplierSettings] = useState<SupplierSettings>(createEmptyBusinessEntity());
|
||||
const [editData, setEditData] = useState<TaxInvoiceDetailData | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (mode === 'edit' && id) {
|
||||
getTaxInvoiceById(id)
|
||||
.then((result) => {
|
||||
if (result.success && result.data) {
|
||||
setEditData(result.data);
|
||||
}
|
||||
})
|
||||
.finally(() => setIsLoading(false));
|
||||
return;
|
||||
}
|
||||
|
||||
Promise.all([getTaxInvoices(), getSupplierSettings()])
|
||||
.then(([invoicesResult, settingsResult]) => {
|
||||
if (invoicesResult.success && invoicesResult.data) {
|
||||
setData(invoicesResult.data);
|
||||
}
|
||||
if (settingsResult.success && settingsResult.data) {
|
||||
setSupplierSettings(settingsResult.data);
|
||||
}
|
||||
})
|
||||
.finally(() => setIsLoading(false));
|
||||
}, [mode, id]);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-[400px]">
|
||||
<div className="text-muted-foreground">로딩 중...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (mode === 'edit' && id) {
|
||||
return (
|
||||
<TaxInvoiceDetail
|
||||
id={id}
|
||||
initialData={editData ?? undefined}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TaxInvoiceIssuancePage
|
||||
initialData={data}
|
||||
initialSupplierSettings={supplierSettings}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { TaxInvoiceManagement } from '@/components/accounting/TaxInvoiceManagement';
|
||||
|
||||
export default function TaxInvoicesPage() {
|
||||
return <TaxInvoiceManagement />;
|
||||
}
|
||||
@@ -1,35 +1,19 @@
|
||||
'use client';
|
||||
|
||||
/**
|
||||
* 카드 상세/수정 페이지 - IntegratedDetailTemplate 적용
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useParams, useSearchParams } from 'next/navigation';
|
||||
import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate';
|
||||
import { cardConfig } from '@/components/hr/CardManagement/cardConfig';
|
||||
import {
|
||||
getCard,
|
||||
updateCard,
|
||||
deleteCard,
|
||||
} from '@/components/hr/CardManagement/actions';
|
||||
import type { Card, CardFormData } from '@/components/hr/CardManagement/types';
|
||||
import type { DetailMode } from '@/components/templates/IntegratedDetailTemplate';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { CardDetail } from '@/components/hr/CardManagement/CardDetail';
|
||||
import { getCard } from '@/components/hr/CardManagement/actions';
|
||||
import type { Card } from '@/components/hr/CardManagement/types';
|
||||
|
||||
export default function CardDetailPage() {
|
||||
const params = useParams();
|
||||
const searchParams = useSearchParams();
|
||||
const cardId = params.id as string;
|
||||
|
||||
const [card, setCard] = useState<Card | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// URL에서 mode 파라미터 확인 (?mode=edit)
|
||||
const urlMode = searchParams.get('mode');
|
||||
const initialMode: DetailMode = urlMode === 'edit' ? 'edit' : 'view';
|
||||
|
||||
// 데이터 로드
|
||||
useEffect(() => {
|
||||
async function loadCard() {
|
||||
setIsLoading(true);
|
||||
@@ -40,49 +24,28 @@ export default function CardDetailPage() {
|
||||
} else {
|
||||
setError(result.error || '카드를 찾을 수 없습니다.');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to load card:', err);
|
||||
} catch {
|
||||
setError('카드 조회 중 오류가 발생했습니다.');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
loadCard();
|
||||
}, [cardId]);
|
||||
|
||||
// 수정 핸들러
|
||||
const handleSubmit = async (data: Record<string, unknown>) => {
|
||||
const result = await updateCard(cardId, data as unknown as CardFormData);
|
||||
return { success: result.success, error: result.error };
|
||||
};
|
||||
|
||||
// 삭제 핸들러
|
||||
const handleDelete = async () => {
|
||||
const result = await deleteCard(cardId);
|
||||
return { success: result.success, error: result.error };
|
||||
};
|
||||
|
||||
// 에러 상태
|
||||
if (error && !isLoading) {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="text-center py-8 text-muted-foreground">
|
||||
{error}
|
||||
</div>
|
||||
<div className="text-center py-8 text-muted-foreground">{error}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<IntegratedDetailTemplate
|
||||
config={cardConfig}
|
||||
mode={initialMode}
|
||||
initialData={(card as unknown as Record<string, unknown>) || undefined}
|
||||
itemId={cardId}
|
||||
<CardDetail
|
||||
card={card ?? undefined}
|
||||
mode="view"
|
||||
isLoading={isLoading}
|
||||
onSubmit={handleSubmit}
|
||||
onDelete={handleDelete}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,29 +2,14 @@
|
||||
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { CardManagement } from '@/components/hr/CardManagement';
|
||||
import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate';
|
||||
import { cardConfig } from '@/components/hr/CardManagement/cardConfig';
|
||||
import { createCard } from '@/components/hr/CardManagement/actions';
|
||||
import type { CardFormData } from '@/components/hr/CardManagement/types';
|
||||
import { CardDetail } from '@/components/hr/CardManagement/CardDetail';
|
||||
|
||||
export default function CardManagementPage() {
|
||||
const searchParams = useSearchParams();
|
||||
const mode = searchParams.get('mode');
|
||||
|
||||
// mode=new일 때 등록 화면 표시
|
||||
if (mode === 'new') {
|
||||
const handleSubmit = async (data: Record<string, unknown>) => {
|
||||
const result = await createCard(data as unknown as CardFormData);
|
||||
return { success: result.success, error: result.error };
|
||||
};
|
||||
|
||||
return (
|
||||
<IntegratedDetailTemplate
|
||||
config={cardConfig}
|
||||
mode="create"
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
);
|
||||
return <CardDetail mode="create" />;
|
||||
}
|
||||
|
||||
return <CardManagement />;
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
'use client';
|
||||
|
||||
/**
|
||||
* 계좌 상세/수정 페이지 - IntegratedDetailTemplate 적용
|
||||
* 계좌 상세/수정 페이지 - AccountDetailForm 적용
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useParams, useSearchParams } from 'next/navigation';
|
||||
import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate';
|
||||
import { accountConfig } from '@/components/settings/AccountManagement/accountConfig';
|
||||
import { AccountDetailForm } from '@/components/settings/AccountManagement/AccountDetailForm';
|
||||
import {
|
||||
getBankAccount,
|
||||
updateBankAccount,
|
||||
deleteBankAccount,
|
||||
} from '@/components/settings/AccountManagement/actions';
|
||||
import type { Account, AccountFormData } from '@/components/settings/AccountManagement/types';
|
||||
import type { DetailMode } from '@/components/templates/IntegratedDetailTemplate';
|
||||
|
||||
export default function AccountDetailPage() {
|
||||
const params = useParams();
|
||||
@@ -25,11 +23,9 @@ export default function AccountDetailPage() {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// URL에서 mode 파라미터 확인 (?mode=edit)
|
||||
const urlMode = searchParams.get('mode');
|
||||
const initialMode: DetailMode = urlMode === 'edit' ? 'edit' : 'view';
|
||||
const initialMode: 'view' | 'edit' = urlMode === 'edit' ? 'edit' : 'view';
|
||||
|
||||
// 데이터 로드
|
||||
useEffect(() => {
|
||||
async function loadAccount() {
|
||||
setIsLoading(true);
|
||||
@@ -40,50 +36,40 @@ export default function AccountDetailPage() {
|
||||
} else {
|
||||
setError(result.error || '계좌를 찾을 수 없습니다.');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to load account:', err);
|
||||
} catch {
|
||||
setError('계좌 조회 중 오류가 발생했습니다.');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
loadAccount();
|
||||
}, [accountId]);
|
||||
|
||||
// 수정 핸들러
|
||||
const handleSubmit = async (data: Record<string, unknown>) => {
|
||||
const result = await updateBankAccount(accountId, data as unknown as Partial<AccountFormData>);
|
||||
const handleSubmit = async (data: AccountFormData) => {
|
||||
const result = await updateBankAccount(accountId, data);
|
||||
return { success: result.success, error: result.error };
|
||||
};
|
||||
|
||||
// 삭제 핸들러
|
||||
const handleDelete = async () => {
|
||||
const result = await deleteBankAccount(accountId);
|
||||
return { success: result.success, error: result.error };
|
||||
};
|
||||
|
||||
// 에러 상태
|
||||
if (error && !isLoading) {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="text-center py-8 text-muted-foreground">
|
||||
{error}
|
||||
</div>
|
||||
<div className="text-center py-8 text-muted-foreground">{error}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<IntegratedDetailTemplate
|
||||
config={accountConfig}
|
||||
<AccountDetailForm
|
||||
mode={initialMode}
|
||||
initialData={(account as unknown as Record<string, unknown>) || undefined}
|
||||
itemId={accountId}
|
||||
initialData={account || undefined}
|
||||
isLoading={isLoading}
|
||||
onSubmit={handleSubmit}
|
||||
onDelete={handleDelete}
|
||||
stickyButtons={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,26 +1,18 @@
|
||||
'use client';
|
||||
|
||||
/**
|
||||
* 계좌 등록 페이지 - IntegratedDetailTemplate 적용
|
||||
* 계좌 등록 페이지 - AccountDetailForm 적용
|
||||
*/
|
||||
|
||||
import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate';
|
||||
import { accountConfig } from '@/components/settings/AccountManagement/accountConfig';
|
||||
import { AccountDetailForm } from '@/components/settings/AccountManagement/AccountDetailForm';
|
||||
import { createBankAccount } from '@/components/settings/AccountManagement/actions';
|
||||
import type { AccountFormData } from '@/components/settings/AccountManagement/types';
|
||||
|
||||
export default function NewAccountPage() {
|
||||
const handleSubmit = async (data: Record<string, unknown>) => {
|
||||
const result = await createBankAccount(data as unknown as AccountFormData);
|
||||
const handleSubmit = async (data: AccountFormData) => {
|
||||
const result = await createBankAccount(data);
|
||||
return { success: result.success, error: result.error };
|
||||
};
|
||||
|
||||
return (
|
||||
<IntegratedDetailTemplate
|
||||
config={accountConfig}
|
||||
mode="create"
|
||||
onSubmit={handleSubmit}
|
||||
stickyButtons={true}
|
||||
/>
|
||||
);
|
||||
return <AccountDetailForm mode="create" onSubmit={handleSubmit} />;
|
||||
}
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { AccountManagement } from '@/components/settings/AccountManagement';
|
||||
import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate';
|
||||
import { accountConfig } from '@/components/settings/AccountManagement/accountConfig';
|
||||
import { AccountDetailForm } from '@/components/settings/AccountManagement/AccountDetailForm';
|
||||
import { createBankAccount } from '@/components/settings/AccountManagement/actions';
|
||||
import type { AccountFormData } from '@/components/settings/AccountManagement/types';
|
||||
|
||||
@@ -12,18 +11,12 @@ export default function AccountsPage() {
|
||||
const mode = searchParams.get('mode');
|
||||
|
||||
if (mode === 'new') {
|
||||
const handleSubmit = async (data: Record<string, unknown>) => {
|
||||
const result = await createBankAccount(data as unknown as AccountFormData);
|
||||
const handleSubmit = async (data: AccountFormData) => {
|
||||
const result = await createBankAccount(data);
|
||||
return { success: result.success, error: result.error };
|
||||
};
|
||||
|
||||
return (
|
||||
<IntegratedDetailTemplate
|
||||
config={accountConfig}
|
||||
mode="create"
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
);
|
||||
return <AccountDetailForm mode="create" onSubmit={handleSubmit} />;
|
||||
}
|
||||
|
||||
return <AccountManagement />;
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { BarobillIntegration } from '@/components/settings/BarobillIntegration';
|
||||
|
||||
export default function BarobillIntegrationPage() {
|
||||
return <BarobillIntegration />;
|
||||
}
|
||||
Reference in New Issue
Block a user