feat(WEB): 결재함 문서 상세 모달 데이터 연동 개선
- ApprovalBox: 문서 클릭 시 API 데이터 로드하여 모달에 표시 - DocumentCreate: 품의서 폼 개선 및 actions 수정 - 결재자 정보 (직책, 부서) 표시 개선
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Mic } from 'lucide-react';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
@@ -8,14 +9,61 @@ import { CurrencyInput } from '@/components/ui/currency-input';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { FileDropzone } from '@/components/ui/file-dropzone';
|
||||
import { FileList, type NewFile, type ExistingFile } from '@/components/ui/file-list';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { getClients } from '@/components/accounting/VendorManagement/actions';
|
||||
import type { ProposalData, UploadedFile } from './types';
|
||||
|
||||
// 거래처 옵션 타입
|
||||
interface ClientOption {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface ProposalFormProps {
|
||||
data: ProposalData;
|
||||
onChange: (data: ProposalData) => void;
|
||||
}
|
||||
|
||||
export function ProposalForm({ data, onChange }: ProposalFormProps) {
|
||||
// 거래처 목록 상태
|
||||
const [clients, setClients] = useState<ClientOption[]>([]);
|
||||
const [isLoadingClients, setIsLoadingClients] = useState(true);
|
||||
|
||||
// 거래처 목록 로드 (매입 거래처만)
|
||||
useEffect(() => {
|
||||
async function loadClients() {
|
||||
setIsLoadingClients(true);
|
||||
const result = await getClients({ size: 1000, only_active: true });
|
||||
if (result.success) {
|
||||
// 매입 거래처(purchase, both)만 필터링
|
||||
const purchaseClients = result.data
|
||||
.filter((v) => v.category === 'purchase' || v.category === 'both')
|
||||
.map((v) => ({
|
||||
id: v.id,
|
||||
name: v.vendorName,
|
||||
}));
|
||||
setClients(purchaseClients);
|
||||
}
|
||||
setIsLoadingClients(false);
|
||||
}
|
||||
loadClients();
|
||||
}, []);
|
||||
|
||||
// 거래처 선택 핸들러
|
||||
const handleVendorChange = (vendorId: string) => {
|
||||
const selected = clients.find((c) => c.id === vendorId);
|
||||
onChange({
|
||||
...data,
|
||||
vendorId,
|
||||
vendor: selected?.name || '',
|
||||
});
|
||||
};
|
||||
const handleFilesSelect = (files: File[]) => {
|
||||
onChange({ ...data, attachments: [...data.attachments, ...files] });
|
||||
};
|
||||
@@ -41,12 +89,22 @@ export function ProposalForm({ data, onChange }: ProposalFormProps) {
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="vendor">구매처</Label>
|
||||
<Input
|
||||
id="vendor"
|
||||
placeholder="구매처를 입력해주세요"
|
||||
value={data.vendor}
|
||||
onChange={(e) => onChange({ ...data, vendor: e.target.value })}
|
||||
/>
|
||||
<Select
|
||||
value={data.vendorId || ''}
|
||||
onValueChange={handleVendorChange}
|
||||
disabled={isLoadingClients}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={isLoadingClients ? '불러오는 중...' : '구매처를 선택해주세요'} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{clients.map((client) => (
|
||||
<SelectItem key={client.id} value={client.id}>
|
||||
{client.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
|
||||
@@ -760,7 +760,8 @@ function transformApiToFormData(apiData: {
|
||||
department,
|
||||
};
|
||||
|
||||
if (step.step_type === 'approval') {
|
||||
// 'approval'과 'agreement' 모두 결재선에 포함
|
||||
if (step.step_type === 'approval' || step.step_type === 'agreement') {
|
||||
approvalLine.push(person);
|
||||
} else if (step.step_type === 'reference') {
|
||||
references.push(person);
|
||||
@@ -808,6 +809,7 @@ function transformApiToFormData(apiData: {
|
||||
|
||||
if (documentType === 'proposal') {
|
||||
proposalData = {
|
||||
vendorId: (content.vendorId as string) || '',
|
||||
vendor: (content.vendor as string) || '',
|
||||
vendorPaymentDate: (content.vendorPaymentDate as string) || '',
|
||||
title: (content.title as string) || '',
|
||||
@@ -894,6 +896,7 @@ function getDocumentContent(
|
||||
switch (formData.basicInfo.documentType) {
|
||||
case 'proposal':
|
||||
return {
|
||||
vendorId: formData.proposalData?.vendorId,
|
||||
vendor: formData.proposalData?.vendor,
|
||||
vendorPaymentDate: formData.proposalData?.vendorPaymentDate,
|
||||
title: formData.proposalData?.title,
|
||||
|
||||
@@ -45,6 +45,7 @@ import type {
|
||||
} from './types';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
import { useDevFill, generatePurchaseApprovalData } from '@/components/dev';
|
||||
import { getClients } from '@/components/accounting/VendorManagement/actions';
|
||||
|
||||
// 초기 데이터 - SSR에서는 빈 문자열, 클라이언트에서 날짜 설정
|
||||
const getInitialBasicInfo = (): BasicInfo => ({
|
||||
@@ -55,6 +56,7 @@ const getInitialBasicInfo = (): BasicInfo => ({
|
||||
});
|
||||
|
||||
const getInitialProposalData = (): ProposalData => ({
|
||||
vendorId: '',
|
||||
vendor: '',
|
||||
vendorPaymentDate: '', // 클라이언트에서 설정
|
||||
title: '',
|
||||
@@ -131,6 +133,19 @@ export function DocumentCreate() {
|
||||
// 직원 목록 가져오기
|
||||
const employees = await getEmployees();
|
||||
|
||||
// 거래처 목록 가져오기 (매입 거래처만)
|
||||
const clientsResult = await getClients({ size: 1000, only_active: true });
|
||||
const purchaseClients = clientsResult.success
|
||||
? clientsResult.data
|
||||
.filter((v) => v.category === 'purchase' || v.category === 'both')
|
||||
.map((v) => ({ id: v.id, name: v.vendorName }))
|
||||
: [];
|
||||
|
||||
// 랜덤 거래처 선택
|
||||
const randomClient = purchaseClients.length > 0
|
||||
? purchaseClients[Math.floor(Math.random() * purchaseClients.length)]
|
||||
: null;
|
||||
|
||||
// localStorage에서 실제 로그인 사용자 이름 가져오기 (우측 상단 표시와 동일한 소스)
|
||||
const userDataStr = localStorage.getItem("user");
|
||||
const currentUserName = userDataStr ? JSON.parse(userDataStr).name : currentUser?.name;
|
||||
@@ -171,6 +186,9 @@ export function DocumentCreate() {
|
||||
setProposalData(prev => ({
|
||||
...prev,
|
||||
...mockData.proposalData,
|
||||
// 실제 API 거래처로 덮어쓰기
|
||||
vendorId: randomClient?.id || '',
|
||||
vendor: randomClient?.name || '',
|
||||
}));
|
||||
toast.success('지출결의서 데이터가 자동 입력되었습니다.');
|
||||
}
|
||||
|
||||
@@ -38,7 +38,8 @@ export interface BasicInfo {
|
||||
|
||||
// 품의서 데이터
|
||||
export interface ProposalData {
|
||||
vendor: string;
|
||||
vendorId: string; // 거래처 ID (API 연동)
|
||||
vendor: string; // 거래처명 (표시용)
|
||||
vendorPaymentDate: string;
|
||||
title: string;
|
||||
description: string;
|
||||
|
||||
Reference in New Issue
Block a user