ERP-node/frontend/components/barcode/designer/BarcodeDesignerToolbar.tsx

180 lines
5.7 KiB
TypeScript
Raw Normal View History

2026-03-04 20:51:00 +09:00
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
} from "@/components/ui/dialog";
import { Label } from "@/components/ui/label";
import { ArrowLeft, Save, Loader2, Download, Printer } from "lucide-react";
import { useBarcodeDesigner } from "@/contexts/BarcodeDesignerContext";
import { barcodeApi } from "@/lib/api/barcodeApi";
import { useToast } from "@/hooks/use-toast";
import { generateZPL } from "@/lib/zplGenerator";
import { BarcodePrintPreviewModal } from "./BarcodePrintPreviewModal";
export function BarcodeDesignerToolbar() {
const router = useRouter();
const { toast } = useToast();
const {
labelId,
labelMaster,
widthMm,
heightMm,
components,
saveLayout,
isSaving,
} = useBarcodeDesigner();
const handleDownloadZPL = () => {
const layout = { width_mm: widthMm, height_mm: heightMm, components };
const zpl = generateZPL(layout);
const blob = new Blob([zpl], { type: "text/plain;charset=utf-8" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = (labelMaster?.label_name_kor || "label") + ".zpl";
a.click();
URL.revokeObjectURL(url);
toast({ title: "다운로드", description: "ZPL 파일이 다운로드되었습니다. Zebra 프린터/유틸에서 사용하세요." });
};
const [saveDialogOpen, setSaveDialogOpen] = useState(false);
const [printPreviewOpen, setPrintPreviewOpen] = useState(false);
const [newLabelName, setNewLabelName] = useState("");
const [creating, setCreating] = useState(false);
const handleSave = async () => {
if (labelId !== "new") {
await saveLayout();
return;
}
setSaveDialogOpen(true);
};
const handleCreateAndSave = async () => {
const name = newLabelName.trim();
if (!name) {
toast({
title: "입력 필요",
description: "라벨명을 입력하세요.",
variant: "destructive",
});
return;
}
setCreating(true);
try {
const createRes = await barcodeApi.createLabel({
labelNameKor: name,
});
if (!createRes.success || !createRes.data?.labelId) throw new Error(createRes.message || "생성 실패");
const newId = createRes.data.labelId;
await barcodeApi.saveLayout(newId, {
width_mm: widthMm,
height_mm: heightMm,
components: components.map((c, i) => ({ ...c, zIndex: i })),
});
toast({ title: "저장됨", description: "라벨이 생성되었습니다." });
setSaveDialogOpen(false);
setNewLabelName("");
router.push(`/admin/screenMng/barcodeList/designer/${newId}`);
} catch (e: any) {
toast({
title: "저장 실패",
description: e.message || "라벨 생성에 실패했습니다.",
variant: "destructive",
});
} finally {
setCreating(false);
}
};
return (
<>
<div className="flex items-center justify-between border-b bg-white px-4 py-2">
<div className="flex items-center gap-2">
<Button
variant="ghost"
size="sm"
className="gap-1"
onClick={() => router.push("/admin/screenMng/barcodeList")}
>
<ArrowLeft className="h-4 w-4" />
</Button>
<span className="text-muted-foreground text-sm">
{labelId === "new" ? "새 라벨" : labelMaster?.label_name_kor || "바코드 라벨 디자이너"}
</span>
</div>
<div className="flex items-center gap-2">
<Button
size="sm"
variant="outline"
className="gap-1"
onClick={() => setPrintPreviewOpen(true)}
>
<Printer className="h-4 w-4" />
</Button>
<Button size="sm" variant="outline" className="gap-1" onClick={handleDownloadZPL}>
<Download className="h-4 w-4" />
ZPL
</Button>
<Button size="sm" className="gap-1" onClick={handleSave} disabled={isSaving || creating}>
{(isSaving || creating) ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
<Save className="h-4 w-4" />
)}
</Button>
</div>
</div>
<BarcodePrintPreviewModal
open={printPreviewOpen}
onOpenChange={setPrintPreviewOpen}
layout={{
width_mm: widthMm,
height_mm: heightMm,
components: components.map((c, i) => ({ ...c, zIndex: i })),
}}
labelName={labelMaster?.label_name_kor || "라벨"}
/>
<Dialog open={saveDialogOpen} onOpenChange={setSaveDialogOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle> </DialogTitle>
</DialogHeader>
<div className="space-y-2 py-2">
<Label> ()</Label>
<Input
value={newLabelName}
onChange={(e) => setNewLabelName(e.target.value)}
placeholder="예: 품목 바코드 라벨"
/>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setSaveDialogOpen(false)}>
</Button>
<Button onClick={handleCreateAndSave} disabled={creating}>
{creating ? <Loader2 className="h-4 w-4 animate-spin" /> : null}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</>
);
}