Merge pull request 'Implement pagination for source data in Outbound, Receiving, and Shipping Order pages' (#433) from jskim-node into main
Reviewed-on: http://39.117.244.52:3000/kjs/ERP-node/pulls/433
This commit is contained in:
commit
45a92de60b
|
|
@ -37,6 +37,9 @@ import {
|
|||
X,
|
||||
Save,
|
||||
ChevronRight,
|
||||
ChevronLeft,
|
||||
ChevronsLeft,
|
||||
ChevronsRight,
|
||||
} from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
|
|
@ -131,6 +134,10 @@ export default function OutboundPage() {
|
|||
const [items, setItems] = useState<ItemSource[]>([]);
|
||||
const [warehouses, setWarehouses] = useState<WarehouseOption[]>([]);
|
||||
|
||||
// 소스 데이터 페이징 (클라이언트 사이드)
|
||||
const [sourcePage, setSourcePage] = useState(1);
|
||||
const [sourcePageSize, setSourcePageSize] = useState(20);
|
||||
|
||||
// 날짜 초기화
|
||||
useEffect(() => {
|
||||
const today = new Date();
|
||||
|
|
@ -261,13 +268,44 @@ export default function OutboundPage() {
|
|||
};
|
||||
|
||||
const searchSourceData = useCallback(async () => {
|
||||
setSourcePage(1);
|
||||
await loadSourceData(modalOutboundType, sourceKeyword || undefined);
|
||||
}, [modalOutboundType, sourceKeyword, loadSourceData]);
|
||||
|
||||
// 현재 출고유형에 따른 전체 소스 데이터
|
||||
const allSourceData = useMemo(() => {
|
||||
if (modalOutboundType === "판매출고") return shipmentInstructions;
|
||||
if (modalOutboundType === "반품출고") return purchaseOrders;
|
||||
return items;
|
||||
}, [modalOutboundType, shipmentInstructions, purchaseOrders, items]);
|
||||
|
||||
const sourceTotalCount = allSourceData.length;
|
||||
const sourceTotalPages = Math.max(1, Math.ceil(sourceTotalCount / sourcePageSize));
|
||||
|
||||
// 현재 페이지에 해당하는 slice
|
||||
const pagedShipmentInstructions = useMemo(() => {
|
||||
if (modalOutboundType !== "판매출고") return [];
|
||||
const start = (sourcePage - 1) * sourcePageSize;
|
||||
return shipmentInstructions.slice(start, start + sourcePageSize);
|
||||
}, [modalOutboundType, shipmentInstructions, sourcePage, sourcePageSize]);
|
||||
|
||||
const pagedPurchaseOrders = useMemo(() => {
|
||||
if (modalOutboundType !== "반품출고") return [];
|
||||
const start = (sourcePage - 1) * sourcePageSize;
|
||||
return purchaseOrders.slice(start, start + sourcePageSize);
|
||||
}, [modalOutboundType, purchaseOrders, sourcePage, sourcePageSize]);
|
||||
|
||||
const pagedItems = useMemo(() => {
|
||||
if (modalOutboundType !== "기타출고") return [];
|
||||
const start = (sourcePage - 1) * sourcePageSize;
|
||||
return items.slice(start, start + sourcePageSize);
|
||||
}, [modalOutboundType, items, sourcePage, sourcePageSize]);
|
||||
|
||||
const handleOutboundTypeChange = useCallback(
|
||||
(type: string) => {
|
||||
setModalOutboundType(type);
|
||||
setSourceKeyword("");
|
||||
setSourcePage(1);
|
||||
setShipmentInstructions([]);
|
||||
setPurchaseOrders([]);
|
||||
setItems([]);
|
||||
|
|
@ -686,6 +724,7 @@ export default function OutboundPage() {
|
|||
defaultMaxWidth="sm:max-w-[1600px]"
|
||||
defaultWidth="w-[95vw]"
|
||||
className="h-[90vh] p-0"
|
||||
contentClassName="overflow-hidden flex flex-col"
|
||||
footer={
|
||||
<div className="flex w-full items-center justify-between px-6 py-3">
|
||||
<div className="text-muted-foreground text-xs">
|
||||
|
|
@ -774,43 +813,87 @@ export default function OutboundPage() {
|
|||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-auto px-4 py-2">
|
||||
<h4 className="text-muted-foreground mb-2 text-xs font-semibold">
|
||||
<div className="flex items-center justify-between border-b px-4 py-2 shrink-0">
|
||||
<h4 className="text-muted-foreground text-xs font-semibold">
|
||||
{modalOutboundType === "판매출고"
|
||||
? "미출고 출하지시 목록"
|
||||
: modalOutboundType === "반품출고"
|
||||
? "입고된 발주 목록"
|
||||
: "품목 목록"}
|
||||
</h4>
|
||||
{sourceTotalCount > 0 && (
|
||||
<span className="text-muted-foreground text-[11px]">총 {sourceTotalCount}건</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-auto">
|
||||
{sourceLoading ? (
|
||||
<div className="flex h-40 items-center justify-center">
|
||||
<Loader2 className="h-5 w-5 animate-spin" />
|
||||
</div>
|
||||
) : modalOutboundType === "판매출고" ? (
|
||||
<SourceShipmentInstructionTable
|
||||
data={shipmentInstructions}
|
||||
data={pagedShipmentInstructions}
|
||||
onAdd={addShipmentInstruction}
|
||||
selectedKeys={selectedItems.map((s) => s.key)}
|
||||
/>
|
||||
) : modalOutboundType === "반품출고" ? (
|
||||
<SourcePurchaseOrderTable
|
||||
data={purchaseOrders}
|
||||
data={pagedPurchaseOrders}
|
||||
onAdd={addPurchaseOrder}
|
||||
selectedKeys={selectedItems.map((s) => s.key)}
|
||||
/>
|
||||
) : (
|
||||
<SourceItemTable
|
||||
data={items}
|
||||
data={pagedItems}
|
||||
onAdd={addItem}
|
||||
selectedKeys={selectedItems.map((s) => s.key)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 페이징 바 */}
|
||||
{sourceTotalCount > 0 && (
|
||||
<div className="flex items-center justify-between border-t bg-muted/10 px-4 py-2 shrink-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-muted-foreground text-[11px]">표시:</span>
|
||||
<Input
|
||||
type="number"
|
||||
min={1}
|
||||
max={500}
|
||||
value={sourcePageSize}
|
||||
onChange={(e) => {
|
||||
const v = parseInt(e.target.value, 10);
|
||||
if (v > 0) { setSourcePageSize(v); setSourcePage(1); }
|
||||
}}
|
||||
className="h-7 w-[60px] text-center text-[11px]"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Button variant="outline" size="icon" className="h-7 w-7" disabled={sourcePage <= 1}
|
||||
onClick={() => setSourcePage(1)}>
|
||||
<ChevronsLeft className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
<Button variant="outline" size="icon" className="h-7 w-7" disabled={sourcePage <= 1}
|
||||
onClick={() => setSourcePage((p) => p - 1)}>
|
||||
<ChevronLeft className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
<span className="text-xs font-medium px-2">{sourcePage} / {sourceTotalPages}</span>
|
||||
<Button variant="outline" size="icon" className="h-7 w-7" disabled={sourcePage >= sourceTotalPages}
|
||||
onClick={() => setSourcePage((p) => p + 1)}>
|
||||
<ChevronRight className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
<Button variant="outline" size="icon" className="h-7 w-7" disabled={sourcePage >= sourceTotalPages}
|
||||
onClick={() => setSourcePage(sourceTotalPages)}>
|
||||
<ChevronsRight className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
|
||||
<ResizableHandle withHandle />
|
||||
<ResizableHandle withHandle onPointerDown={(e) => e.stopPropagation()} />
|
||||
|
||||
{/* 우측: 출고 정보 + 선택 품목 */}
|
||||
<ResizablePanel defaultSize={40} minSize={25}>
|
||||
|
|
|
|||
|
|
@ -37,6 +37,9 @@ import {
|
|||
X,
|
||||
Save,
|
||||
ChevronRight,
|
||||
ChevronLeft,
|
||||
ChevronsLeft,
|
||||
ChevronsRight,
|
||||
} from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
|
|
@ -132,6 +135,11 @@ export default function ReceivingPage() {
|
|||
const [items, setItems] = useState<ItemSource[]>([]);
|
||||
const [warehouses, setWarehouses] = useState<WarehouseOption[]>([]);
|
||||
|
||||
// 소스 데이터 페이징
|
||||
const [sourcePage, setSourcePage] = useState(1);
|
||||
const [sourcePageSize, setSourcePageSize] = useState(20);
|
||||
const [sourceTotalCount, setSourceTotalCount] = useState(0);
|
||||
|
||||
// 날짜 초기화
|
||||
useEffect(() => {
|
||||
const today = new Date();
|
||||
|
|
@ -214,18 +222,32 @@ export default function ReceivingPage() {
|
|||
|
||||
// 소스 데이터 로드 함수
|
||||
const loadSourceData = useCallback(
|
||||
async (type: string, keyword?: string) => {
|
||||
async (type: string, keyword?: string, pageOverride?: number) => {
|
||||
setSourceLoading(true);
|
||||
try {
|
||||
const params = {
|
||||
keyword: keyword || undefined,
|
||||
page: pageOverride ?? sourcePage,
|
||||
pageSize: sourcePageSize,
|
||||
};
|
||||
if (type === "구매입고") {
|
||||
const res = await getPurchaseOrderSources(keyword || undefined);
|
||||
if (res.success) setPurchaseOrders(res.data);
|
||||
const res = await getPurchaseOrderSources(params);
|
||||
if (res.success) {
|
||||
setPurchaseOrders(res.data);
|
||||
setSourceTotalCount(res.totalCount || 0);
|
||||
}
|
||||
} else if (type === "반품입고") {
|
||||
const res = await getShipmentSources(keyword || undefined);
|
||||
if (res.success) setShipments(res.data);
|
||||
const res = await getShipmentSources(params);
|
||||
if (res.success) {
|
||||
setShipments(res.data);
|
||||
setSourceTotalCount(res.totalCount || 0);
|
||||
}
|
||||
} else {
|
||||
const res = await getItemSources(keyword || undefined);
|
||||
if (res.success) setItems(res.data);
|
||||
const res = await getItemSources(params);
|
||||
if (res.success) {
|
||||
setItems(res.data);
|
||||
setSourceTotalCount(res.totalCount || 0);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
|
|
@ -233,7 +255,7 @@ export default function ReceivingPage() {
|
|||
setSourceLoading(false);
|
||||
}
|
||||
},
|
||||
[]
|
||||
[sourcePage, sourcePageSize]
|
||||
);
|
||||
|
||||
const openRegisterModal = async () => {
|
||||
|
|
@ -250,13 +272,15 @@ export default function ReceivingPage() {
|
|||
setPurchaseOrders([]);
|
||||
setShipments([]);
|
||||
setItems([]);
|
||||
setSourcePage(1);
|
||||
setSourceTotalCount(0);
|
||||
setIsModalOpen(true);
|
||||
|
||||
// 입고번호 생성 + 발주 데이터 동시 로드
|
||||
try {
|
||||
const [numRes] = await Promise.all([
|
||||
generateReceivingNumber(),
|
||||
loadSourceData(defaultType),
|
||||
loadSourceData(defaultType, undefined, 1),
|
||||
]);
|
||||
if (numRes.success) setModalInboundNo(numRes.data);
|
||||
} catch {
|
||||
|
|
@ -266,7 +290,8 @@ export default function ReceivingPage() {
|
|||
|
||||
// 검색 버튼 클릭 시
|
||||
const searchSourceData = useCallback(async () => {
|
||||
await loadSourceData(modalInboundType, sourceKeyword || undefined);
|
||||
setSourcePage(1);
|
||||
await loadSourceData(modalInboundType, sourceKeyword || undefined, 1);
|
||||
}, [modalInboundType, sourceKeyword, loadSourceData]);
|
||||
|
||||
// 입고유형 변경 시 소스 데이터 자동 리로드
|
||||
|
|
@ -278,7 +303,9 @@ export default function ReceivingPage() {
|
|||
setShipments([]);
|
||||
setItems([]);
|
||||
setSelectedItems([]);
|
||||
loadSourceData(type);
|
||||
setSourcePage(1);
|
||||
setSourceTotalCount(0);
|
||||
loadSourceData(type, undefined, 1);
|
||||
},
|
||||
[loadSourceData]
|
||||
);
|
||||
|
|
@ -303,7 +330,7 @@ export default function ReceivingPage() {
|
|||
inbound_qty: po.remain_qty,
|
||||
unit_price: po.unit_price,
|
||||
total_amount: po.remain_qty * po.unit_price,
|
||||
source_table: "purchase_order_mng",
|
||||
source_table: po.source_table || "purchase_order_mng",
|
||||
source_id: po.id,
|
||||
},
|
||||
]);
|
||||
|
|
@ -694,6 +721,7 @@ export default function ReceivingPage() {
|
|||
defaultMaxWidth="sm:max-w-[1600px]"
|
||||
defaultWidth="w-[95vw]"
|
||||
className="h-[90vh] p-0"
|
||||
contentClassName="overflow-hidden flex flex-col"
|
||||
footer={
|
||||
<div className="flex w-full items-center justify-between px-6 py-3">
|
||||
<div className="text-muted-foreground text-xs">
|
||||
|
|
@ -817,10 +845,56 @@ export default function ReceivingPage() {
|
|||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 페이징 */}
|
||||
{sourceTotalCount > 0 && (
|
||||
<div className="flex shrink-0 items-center justify-between border-t bg-muted/10 px-4 py-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-muted-foreground text-[11px]">표시:</span>
|
||||
<Input
|
||||
type="number"
|
||||
min={1}
|
||||
max={500}
|
||||
value={sourcePageSize}
|
||||
onChange={(e) => {
|
||||
const v = parseInt(e.target.value, 10);
|
||||
if (v > 0) {
|
||||
setSourcePageSize(v);
|
||||
setSourcePage(1);
|
||||
loadSourceData(modalInboundType, sourceKeyword || undefined, 1);
|
||||
}
|
||||
}}
|
||||
className="h-7 w-[60px] text-center text-[11px]"
|
||||
/>
|
||||
<span className="text-muted-foreground text-[11px]">
|
||||
총 {sourceTotalCount}건
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Button variant="outline" size="icon" className="h-7 w-7" disabled={sourcePage <= 1}
|
||||
onClick={() => { setSourcePage(1); loadSourceData(modalInboundType, sourceKeyword || undefined, 1); }}>
|
||||
<ChevronsLeft className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
<Button variant="outline" size="icon" className="h-7 w-7" disabled={sourcePage <= 1}
|
||||
onClick={() => { const p = sourcePage - 1; setSourcePage(p); loadSourceData(modalInboundType, sourceKeyword || undefined, p); }}>
|
||||
<ChevronLeft className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
<span className="px-2 text-xs font-medium">{sourcePage} / {Math.max(1, Math.ceil(sourceTotalCount / sourcePageSize))}</span>
|
||||
<Button variant="outline" size="icon" className="h-7 w-7" disabled={sourcePage >= Math.ceil(sourceTotalCount / sourcePageSize)}
|
||||
onClick={() => { const p = sourcePage + 1; setSourcePage(p); loadSourceData(modalInboundType, sourceKeyword || undefined, p); }}>
|
||||
<ChevronRight className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
<Button variant="outline" size="icon" className="h-7 w-7" disabled={sourcePage >= Math.ceil(sourceTotalCount / sourcePageSize)}
|
||||
onClick={() => { const p = Math.ceil(sourceTotalCount / sourcePageSize); setSourcePage(p); loadSourceData(modalInboundType, sourceKeyword || undefined, p); }}>
|
||||
<ChevronsRight className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
|
||||
<ResizableHandle withHandle />
|
||||
<ResizableHandle withHandle onPointerDown={(e) => e.stopPropagation()} />
|
||||
|
||||
{/* 우측: 입고 정보 + 선택 품목 */}
|
||||
<ResizablePanel defaultSize={40} minSize={25}>
|
||||
|
|
@ -1030,7 +1104,7 @@ function SourcePurchaseOrderTable({
|
|||
|
||||
return (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableHeader className="sticky top-0 bg-background z-10">
|
||||
<TableRow className="text-[11px]">
|
||||
<TableHead className="w-[40px] p-2" />
|
||||
<TableHead className="p-2">발주번호</TableHead>
|
||||
|
|
@ -1109,7 +1183,7 @@ function SourceShipmentTable({
|
|||
|
||||
return (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableHeader className="sticky top-0 bg-background z-10">
|
||||
<TableRow className="text-[11px]">
|
||||
<TableHead className="w-[40px] p-2" />
|
||||
<TableHead className="p-2">출하번호</TableHead>
|
||||
|
|
@ -1186,7 +1260,7 @@ function SourceItemTable({
|
|||
|
||||
return (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableHeader className="sticky top-0 bg-background z-10">
|
||||
<TableRow className="text-[11px]">
|
||||
<TableHead className="w-[40px] p-2" />
|
||||
<TableHead className="p-2">품목</TableHead>
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ export default function SalesItemPage() {
|
|||
const [customerLoading, setCustomerLoading] = useState(false);
|
||||
|
||||
// 카테고리
|
||||
const [categoryOptions, setCategoryOptions] = useState<Record<string, { code: string; label: string }[]>>({});
|
||||
const [categoryOptions, setCategoryOptions] = useState<Record<string, { code: string; label: string; isDefault?: boolean }[]>>({});
|
||||
|
||||
// 거래처 추가 모달
|
||||
const [custSelectOpen, setCustSelectOpen] = useState(false);
|
||||
|
|
@ -125,11 +125,11 @@ export default function SalesItemPage() {
|
|||
// 카테고리 로드
|
||||
useEffect(() => {
|
||||
const load = async () => {
|
||||
const optMap: Record<string, { code: string; label: string }[]> = {};
|
||||
const flatten = (vals: any[]): { code: string; label: string }[] => {
|
||||
const result: { code: string; label: string }[] = [];
|
||||
const optMap: Record<string, { code: string; label: string; isDefault?: boolean }[]> = {};
|
||||
const flatten = (vals: any[]): { code: string; label: string; isDefault?: boolean }[] => {
|
||||
const result: { code: string; label: string; isDefault?: boolean }[] = [];
|
||||
for (const v of vals) {
|
||||
result.push({ code: v.valueCode, label: v.valueLabel });
|
||||
result.push({ code: v.valueCode, label: v.valueLabel, isDefault: v.isDefault });
|
||||
if (v.children?.length) result.push(...flatten(v.children));
|
||||
}
|
||||
return result;
|
||||
|
|
@ -164,7 +164,11 @@ export default function SalesItemPage() {
|
|||
const fetchItems = useCallback(async () => {
|
||||
setItemLoading(true);
|
||||
try {
|
||||
const filters = searchFilters.map((f) => ({ columnName: f.columnName, operator: f.operator, value: f.value }));
|
||||
const filters: { columnName: string; operator: string; value: any }[] = searchFilters.map((f) => ({ columnName: f.columnName, operator: f.operator, value: f.value }));
|
||||
|
||||
// 판매품목 division 필터 (다중값 컬럼이므로 contains로 매칭)
|
||||
filters.push({ columnName: "division", operator: "contains", value: "CAT_DIV_SALES" });
|
||||
|
||||
const res = await apiClient.post(`/table-management/tables/${ITEM_TABLE}/data`, {
|
||||
page: 1, size: 500,
|
||||
dataFilter: filters.length > 0 ? { enabled: true, filters } : undefined,
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogD
|
|||
import { Label } from "@/components/ui/label";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
|
||||
import { Plus, Trash2, RotateCcw, Save, X, ChevronDown, ChevronRight, ChevronLeft, Truck, Search, Loader2, FileSpreadsheet } from "lucide-react";
|
||||
import { Plus, Trash2, RotateCcw, Save, X, ChevronDown, ChevronRight, ChevronLeft, ChevronsLeft, ChevronsRight, Truck, Search, Loader2, FileSpreadsheet } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { FormDatePicker } from "@/components/screen/filters/FormDatePicker";
|
||||
import {
|
||||
|
|
@ -117,7 +117,7 @@ export default function ShippingOrderPage() {
|
|||
const [sourceLoading, setSourceLoading] = useState(false);
|
||||
const [selectedItems, setSelectedItems] = useState<SelectedItem[]>([]);
|
||||
const [sourcePage, setSourcePage] = useState(1);
|
||||
const [sourcePageSize] = useState(20);
|
||||
const [sourcePageSize, setSourcePageSize] = useState(20);
|
||||
const [sourceTotalCount, setSourceTotalCount] = useState(0);
|
||||
|
||||
// 텍스트 입력 debounce (500ms)
|
||||
|
|
@ -592,6 +592,8 @@ export default function ShippingOrderPage() {
|
|||
description={isEditMode ? "출하지시 정보를 수정합니다." : "왼쪽에서 데이터를 선택하고 오른쪽에서 출하지시 정보를 입력하세요."}
|
||||
defaultMaxWidth="max-w-[90vw]"
|
||||
defaultWidth="w-[1400px]"
|
||||
className="h-[90vh]"
|
||||
contentClassName="overflow-hidden flex flex-col"
|
||||
footer={
|
||||
<>
|
||||
<Button variant="outline" onClick={() => setIsModalOpen(false)}>취소</Button>
|
||||
|
|
@ -694,10 +696,28 @@ export default function ShippingOrderPage() {
|
|||
{/* 페이징 */}
|
||||
{sourceTotalCount > 0 && (
|
||||
<div className="px-4 py-2 border-t bg-muted/10 flex items-center justify-between shrink-0">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
총 {sourceTotalCount}건 중 {(sourcePage - 1) * sourcePageSize + 1}-{Math.min(sourcePage * sourcePageSize, sourceTotalCount)}건
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-muted-foreground text-[11px]">표시:</span>
|
||||
<Input
|
||||
type="number"
|
||||
min={1}
|
||||
max={500}
|
||||
value={sourcePageSize}
|
||||
onChange={(e) => {
|
||||
const v = parseInt(e.target.value, 10);
|
||||
if (v > 0) { setSourcePageSize(v); setSourcePage(1); fetchSourceData(1); }
|
||||
}}
|
||||
className="h-7 w-[60px] text-center text-[11px]"
|
||||
/>
|
||||
<span className="text-muted-foreground text-[11px]">
|
||||
총 {sourceTotalCount}건
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Button variant="outline" size="icon" className="h-7 w-7" disabled={sourcePage <= 1}
|
||||
onClick={() => { setSourcePage(1); fetchSourceData(1); }}>
|
||||
<ChevronsLeft className="w-3.5 h-3.5" />
|
||||
</Button>
|
||||
<Button variant="outline" size="icon" className="h-7 w-7" disabled={sourcePage <= 1}
|
||||
onClick={() => { const p = sourcePage - 1; setSourcePage(p); fetchSourceData(p); }}>
|
||||
<ChevronLeft className="w-3.5 h-3.5" />
|
||||
|
|
@ -707,13 +727,17 @@ export default function ShippingOrderPage() {
|
|||
onClick={() => { const p = sourcePage + 1; setSourcePage(p); fetchSourceData(p); }}>
|
||||
<ChevronRight className="w-3.5 h-3.5" />
|
||||
</Button>
|
||||
<Button variant="outline" size="icon" className="h-7 w-7" disabled={sourcePage >= Math.ceil(sourceTotalCount / sourcePageSize)}
|
||||
onClick={() => { const p = Math.ceil(sourceTotalCount / sourcePageSize); setSourcePage(p); fetchSourceData(p); }}>
|
||||
<ChevronsRight className="w-3.5 h-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
|
||||
<ResizableHandle withHandle />
|
||||
<ResizableHandle withHandle onPointerDown={(e) => e.stopPropagation()} />
|
||||
|
||||
{/* 오른쪽: 폼 */}
|
||||
<ResizablePanel defaultSize={45} minSize={30}>
|
||||
|
|
|
|||
Loading…
Reference in New Issue