- Added validation to prevent duplicate approval requests for the same target, ensuring that only one active or completed approval exists at a time. - Implemented a check to disallow self-approval in the approval line unless the approval type is 'self'. - Integrated the ApprovalDetailModal component into the main layout for improved user experience. - Updated the SalesOrderPage to include approval status in the data structure, enhancing visibility of approval states. - Enhanced BOM management modals across multiple company implementations to accommodate new UI requirements.
98 lines
2.7 KiB
TypeScript
98 lines
2.7 KiB
TypeScript
"use client";
|
|
|
|
import React, { useCallback } from "react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Search } from "lucide-react";
|
|
import { toast } from "sonner";
|
|
|
|
interface AddressSearchButtonProps {
|
|
onComplete: (data: {
|
|
address: string; // 선택된 주소 (도로명 또는 지번)
|
|
roadAddress: string; // 도로명주소
|
|
jibunAddress: string; // 지번주소
|
|
zonecode: string; // 우편번호
|
|
buildingName?: string; // 건물명
|
|
}) => void;
|
|
size?: "sm" | "default" | "lg";
|
|
variant?: "default" | "outline" | "secondary" | "ghost";
|
|
className?: string;
|
|
label?: string;
|
|
}
|
|
|
|
declare global {
|
|
interface Window {
|
|
daum?: any;
|
|
}
|
|
}
|
|
|
|
const SCRIPT_SRC = "//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js";
|
|
|
|
// 스크립트 로드 (1회)
|
|
function loadScript(): Promise<void> {
|
|
return new Promise((resolve, reject) => {
|
|
if (typeof window === "undefined") return reject(new Error("SSR"));
|
|
if (window.daum?.Postcode) return resolve();
|
|
const existing = document.querySelector(`script[src="${SCRIPT_SRC}"]`) as HTMLScriptElement | null;
|
|
if (existing) {
|
|
existing.addEventListener("load", () => resolve());
|
|
existing.addEventListener("error", () => reject(new Error("load failed")));
|
|
return;
|
|
}
|
|
const script = document.createElement("script");
|
|
script.src = SCRIPT_SRC;
|
|
script.async = true;
|
|
script.onload = () => resolve();
|
|
script.onerror = () => reject(new Error("load failed"));
|
|
document.body.appendChild(script);
|
|
});
|
|
}
|
|
|
|
export const AddressSearchButton: React.FC<AddressSearchButtonProps> = ({
|
|
onComplete,
|
|
size = "sm",
|
|
variant = "outline",
|
|
className,
|
|
label = "주소 검색",
|
|
}) => {
|
|
const handleClick = useCallback(async () => {
|
|
try {
|
|
await loadScript();
|
|
if (!window.daum?.Postcode) {
|
|
toast.error("주소 검색 서비스를 불러올 수 없습니다.");
|
|
return;
|
|
}
|
|
new window.daum.Postcode({
|
|
oncomplete: (data: any) => {
|
|
const road = data.roadAddress || "";
|
|
const jibun = data.jibunAddress || data.autoJibunAddress || "";
|
|
const picked = road || jibun;
|
|
onComplete({
|
|
address: picked,
|
|
roadAddress: road,
|
|
jibunAddress: jibun,
|
|
zonecode: data.zonecode || "",
|
|
buildingName: data.buildingName || "",
|
|
});
|
|
},
|
|
}).open();
|
|
} catch {
|
|
toast.error("주소 검색 스크립트 로드에 실패했습니다.");
|
|
}
|
|
}, [onComplete]);
|
|
|
|
return (
|
|
<Button
|
|
type="button"
|
|
variant={variant}
|
|
size={size}
|
|
onClick={handleClick}
|
|
className={className}
|
|
>
|
|
<Search className="w-3.5 h-3.5" />
|
|
{label}
|
|
</Button>
|
|
);
|
|
};
|
|
|
|
export default AddressSearchButton;
|