share job
This commit is contained in:
107
frontend/src/app/(main)/fedex/0010/[sq]/page.tsx
Normal file
107
frontend/src/app/(main)/fedex/0010/[sq]/page.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
'use client';
|
||||
|
||||
import { use } from 'react';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { getFedexDetail, updateFedexAttach } from '@/lib/api/fedex';
|
||||
import FileUpload from '@/components/common/FileUpload';
|
||||
import Link from 'next/link';
|
||||
|
||||
function Field({ label, value }: { label: string; value?: string | number | null }) {
|
||||
return (
|
||||
<div>
|
||||
<dt className="text-xs font-medium text-gray-500 mb-0.5">{label}</dt>
|
||||
<dd className="text-sm text-gray-900">{value ?? '-'}</dd>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Fedex0010DetailPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ sq: string }>;
|
||||
}) {
|
||||
const { sq } = use(params);
|
||||
const router = useRouter();
|
||||
const qc = useQueryClient();
|
||||
|
||||
const { data: item, isLoading } = useQuery({
|
||||
queryKey: ['fedex', 'detail', sq],
|
||||
queryFn: () => getFedexDetail(Number(sq)),
|
||||
enabled: !!sq,
|
||||
});
|
||||
|
||||
const attachMut = useMutation({
|
||||
mutationFn: (atchNo: string) => updateFedexAttach(Number(sq), atchNo),
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: ['fedex', 'detail', sq] }),
|
||||
});
|
||||
|
||||
if (isLoading) return <div className="p-4 text-gray-400">로딩 중...</div>;
|
||||
if (!item) return <div className="p-4 text-gray-400">데이터를 찾을 수 없습니다.</div>;
|
||||
|
||||
return (
|
||||
<div className="p-4 max-w-2xl">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h1 className="text-xl font-bold">수입정정 상세</h1>
|
||||
<div className="flex gap-2">
|
||||
<Link
|
||||
href="/fedex/0010"
|
||||
className="px-3 py-1.5 text-sm border rounded hover:bg-gray-50"
|
||||
>
|
||||
목록
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white border rounded-lg p-5 space-y-5">
|
||||
{/* 신고 정보 */}
|
||||
<section>
|
||||
<h2 className="text-sm font-semibold text-gray-700 mb-3 pb-1 border-b">신고 정보</h2>
|
||||
<dl className="grid grid-cols-2 gap-x-6 gap-y-3">
|
||||
<Field label="신고번호" value={item.singoNo} />
|
||||
<Field label="업체명" value={item.comNm} />
|
||||
<Field label="원신고일" value={item.jSingoDt} />
|
||||
<Field label="정정신고일" value={item.sSingoDt} />
|
||||
<Field label="관세청 구분" value={item.jGwiDes || item.jGwiCd} />
|
||||
<Field label="신고 구분" value={item.ieGbn === 'I' ? '수입' : '수출'} />
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
{/* 정정 정보 */}
|
||||
<section>
|
||||
<h2 className="text-sm font-semibold text-gray-700 mb-3 pb-1 border-b">정정 정보</h2>
|
||||
<dl className="grid grid-cols-2 gap-x-6 gap-y-3">
|
||||
<Field label="처리상태" value={item.fStDes || item.fStCd} />
|
||||
<Field label="정정사유" value={item.fJjDes1 || item.fJjCd} />
|
||||
<Field label="담당자" value={item.fJjNm} />
|
||||
<Field label="처리일" value={item.fJjRegDt} />
|
||||
<div className="col-span-2">
|
||||
<dt className="text-xs font-medium text-gray-500 mb-0.5">정정내용</dt>
|
||||
<dd className="text-sm text-gray-900 whitespace-pre-wrap bg-gray-50 rounded p-3 min-h-16">
|
||||
{item.fJjContents || '-'}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
{/* 등록자 */}
|
||||
<section>
|
||||
<h2 className="text-sm font-semibold text-gray-700 mb-3 pb-1 border-b">등록 정보</h2>
|
||||
<dl className="grid grid-cols-2 gap-x-6 gap-y-3">
|
||||
<Field label="등록자 ID" value={item.regUserId} />
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
{/* 첨부파일 */}
|
||||
<section>
|
||||
<h2 className="text-sm font-semibold text-gray-700 mb-3 pb-1 border-b">첨부파일</h2>
|
||||
<FileUpload
|
||||
atchNo={item.attachNo || undefined}
|
||||
division="FEDEX"
|
||||
onChange={(atchNo) => attachMut.mutate(atchNo)}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
121
frontend/src/app/(main)/fedex/0010/page.tsx
Normal file
121
frontend/src/app/(main)/fedex/0010/page.tsx
Normal file
@@ -0,0 +1,121 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { getFedexList, type FedexItem } from '@/lib/api/fedex';
|
||||
import Pagination from '@/components/common/Pagination';
|
||||
import Link from 'next/link';
|
||||
|
||||
export default function Fedex0010Page() {
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const [inputText, setInputText] = useState('');
|
||||
const [pageNo, setPageNo] = useState(1);
|
||||
const pageSize = 20;
|
||||
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: ['fedex', 'list', searchText, pageNo],
|
||||
queryFn: () => getFedexList({ searchText, pageNo, pageSize }),
|
||||
});
|
||||
|
||||
const list: FedexItem[] = data?.list ?? [];
|
||||
const total = data?.total ?? 0;
|
||||
const totalPages = Math.ceil(total / pageSize);
|
||||
|
||||
const handleSearch = () => {
|
||||
setSearchText(inputText);
|
||||
setPageNo(1);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h1 className="text-xl font-bold">수입정정 관리</h1>
|
||||
<Link
|
||||
href="/fedex/0010/write"
|
||||
className="px-4 py-2 bg-blue-600 text-white text-sm rounded hover:bg-blue-700"
|
||||
>
|
||||
등록
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* 검색 */}
|
||||
<div className="flex gap-2 mb-4">
|
||||
<input
|
||||
type="text"
|
||||
value={inputText}
|
||||
onChange={(e) => setInputText(e.target.value)}
|
||||
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
|
||||
placeholder="업체명 검색"
|
||||
className="border rounded px-3 py-1.5 text-sm w-64 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
<button
|
||||
onClick={handleSearch}
|
||||
className="px-3 py-1.5 bg-gray-700 text-white text-sm rounded hover:bg-gray-800"
|
||||
>
|
||||
검색
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 목록 */}
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm border-collapse">
|
||||
<thead>
|
||||
<tr className="bg-gray-50 border-y">
|
||||
<th className="px-3 py-2 text-left font-medium text-gray-600">신고번호</th>
|
||||
<th className="px-3 py-2 text-left font-medium text-gray-600">업체명</th>
|
||||
<th className="px-3 py-2 text-left font-medium text-gray-600">신고일</th>
|
||||
<th className="px-3 py-2 text-left font-medium text-gray-600">정정신고일</th>
|
||||
<th className="px-3 py-2 text-left font-medium text-gray-600">처리상태</th>
|
||||
<th className="px-3 py-2 text-left font-medium text-gray-600">정정사유</th>
|
||||
<th className="px-3 py-2 text-left font-medium text-gray-600">담당자</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{isLoading ? (
|
||||
<tr>
|
||||
<td colSpan={7} className="text-center py-8 text-gray-400">
|
||||
로딩 중...
|
||||
</td>
|
||||
</tr>
|
||||
) : list.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={7} className="text-center py-8 text-gray-400">
|
||||
데이터가 없습니다.
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
list.map((item) => (
|
||||
<tr key={item.sq} className="border-b hover:bg-gray-50">
|
||||
<td className="px-3 py-2">
|
||||
<Link
|
||||
href={`/fedex/0010/${item.sq}`}
|
||||
className="text-blue-600 hover:underline"
|
||||
>
|
||||
{item.singoNo}
|
||||
</Link>
|
||||
</td>
|
||||
<td className="px-3 py-2">{item.comNm}</td>
|
||||
<td className="px-3 py-2">{item.jSingoDt}</td>
|
||||
<td className="px-3 py-2">{item.sSingoDt}</td>
|
||||
<td className="px-3 py-2">
|
||||
<span className="px-2 py-0.5 rounded-full text-xs bg-gray-100 text-gray-700">
|
||||
{item.fStDes || item.fStCd}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-3 py-2">{item.fJjDes1 || item.fJjCd}</td>
|
||||
<td className="px-3 py-2">{item.regUserId}</td>
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between mt-2 text-sm text-gray-500">
|
||||
<span>총 {total.toLocaleString()}건</span>
|
||||
</div>
|
||||
|
||||
<Pagination currentPage={pageNo} totalPages={totalPages} onPageChange={setPageNo} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
187
frontend/src/app/(main)/fedex/0010/write/page.tsx
Normal file
187
frontend/src/app/(main)/fedex/0010/write/page.tsx
Normal file
@@ -0,0 +1,187 @@
|
||||
'use client';
|
||||
|
||||
import { useQuery, useMutation } from '@tanstack/react-query';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { getFedexCodes, insertFedex } from '@/lib/api/fedex';
|
||||
import { useToast } from '@/components/common/Toast';
|
||||
|
||||
interface FormValues {
|
||||
ieGbn: string;
|
||||
sCode: string;
|
||||
sYear: string;
|
||||
sJechl: string;
|
||||
comNm: string;
|
||||
jSingoDt: string;
|
||||
sSingoDt: string;
|
||||
jGwiCd: string;
|
||||
fStCd: string;
|
||||
fJjCd: string;
|
||||
fJjContents: string;
|
||||
fJjNm: string;
|
||||
fJjRegDt: string;
|
||||
fJjRegGbn: string;
|
||||
}
|
||||
|
||||
export default function Fedex0010WritePage() {
|
||||
const router = useRouter();
|
||||
const { success, error: toastError } = useToast();
|
||||
|
||||
const { data: codes } = useQuery({
|
||||
queryKey: ['fedex', 'codes'],
|
||||
queryFn: getFedexCodes,
|
||||
});
|
||||
|
||||
const { register, handleSubmit, formState: { errors } } = useForm<FormValues>({
|
||||
defaultValues: { fJjRegGbn: '1' },
|
||||
});
|
||||
|
||||
const saveMut = useMutation({
|
||||
mutationFn: (data: FormValues) => insertFedex(data as Record<string, unknown>),
|
||||
onSuccess: (sq) => {
|
||||
success('등록되었습니다.');
|
||||
router.push(`/fedex/0010/${sq}`);
|
||||
},
|
||||
onError: () => toastError('등록 중 오류가 발생했습니다.'),
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="p-4 max-w-2xl">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h1 className="text-xl font-bold">수입정정 등록</h1>
|
||||
<button
|
||||
onClick={() => router.back()}
|
||||
className="text-sm text-gray-500 hover:text-gray-700"
|
||||
>
|
||||
← 목록
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit((d) => saveMut.mutate(d))} className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">신고 구분</label>
|
||||
<select
|
||||
{...register('ieGbn')}
|
||||
className="w-full border rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<option value="I">수입</option>
|
||||
<option value="E">수출</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">업체명 *</label>
|
||||
<input
|
||||
{...register('comNm', { required: '업체명을 입력하세요.' })}
|
||||
className="w-full border rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
{errors.comNm && <p className="text-red-500 text-xs mt-1">{errors.comNm.message}</p>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">신고번호 코드</label>
|
||||
<input {...register('sCode')} className="w-full border rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">연도</label>
|
||||
<input {...register('sYear')} placeholder="2024" className="w-full border rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">제출번호</label>
|
||||
<input {...register('sJechl')} className="w-full border rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">원신고일</label>
|
||||
<input type="date" {...register('jSingoDt')} className="w-full border rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">정정신고일</label>
|
||||
<input type="date" {...register('sSingoDt')} className="w-full border rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">관세청 구분</label>
|
||||
<select
|
||||
{...register('jGwiCd')}
|
||||
className="w-full border rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white"
|
||||
>
|
||||
<option value="">선택</option>
|
||||
{codes?.gwiList.map((g) => (
|
||||
<option key={g.jGwiCd} value={g.jGwiCd}>{g.jGwiDes}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">처리상태</label>
|
||||
<select
|
||||
{...register('fStCd')}
|
||||
className="w-full border rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white"
|
||||
>
|
||||
<option value="">선택</option>
|
||||
{codes?.fstList.map((f) => (
|
||||
<option key={f.fStCd} value={f.fStCd}>{f.fStDes}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">정정사유</label>
|
||||
<select
|
||||
{...register('fJjCd')}
|
||||
className="w-full border rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white"
|
||||
>
|
||||
<option value="">선택</option>
|
||||
{codes?.fjjList.map((f) => (
|
||||
<option key={f.fJjCd} value={f.fJjCd}>{f.fJjDes1}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">정정내용</label>
|
||||
<textarea
|
||||
{...register('fJjContents')}
|
||||
rows={4}
|
||||
className="w-full border rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">담당자명</label>
|
||||
<input {...register('fJjNm')} className="w-full border rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">처리일</label>
|
||||
<input type="date" {...register('fJjRegDt')} className="w-full border rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-2 pt-2 border-t">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => router.back()}
|
||||
className="px-4 py-2 text-sm border rounded hover:bg-gray-50"
|
||||
>
|
||||
취소
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={saveMut.isPending}
|
||||
className="px-4 py-2 text-sm bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50"
|
||||
>
|
||||
{saveMut.isPending ? '저장 중...' : '저장'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user