[부서 안 선택되던 문제] - /api/admin/dept 가 DEPT_CODE/DEPT_NAME 대문자 반환인데 폼은 dept_code 소문자 로 접근 → 옵션 매칭 실패. 대문자로 통일 [필드 제거] - 사번(sabun) 입력 제거 (요청) - dead var isCustomer 제거 [레이아웃 컴팩트화 — 스크롤 없이 한 화면] - 폰트 13→12, 인풋 h-9→h-8, 여백/마진 축소 - 출고 기준 창고 + 특수 권한 섹션을 별도 큰 카드 → 2열 그리드 안에 통합 - 특수 권한 체크 라벨도 컴팩트 (가로형 inline 칩) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,7 +11,7 @@ function UserForm() {
|
||||
const userId = searchParams.get("userId");
|
||||
const isNew = !userId;
|
||||
const [form, setForm] = useState<Record<string, string>>({});
|
||||
const [depts, setDepts] = useState<{ dept_code: string; dept_name: string }[]>([]);
|
||||
const [depts, setDepts] = useState<{ DEPT_CODE: string; DEPT_NAME: string }[]>([]);
|
||||
const [whs, setWhs] = useState<{ OBJID: string; WH_NAME: string; WH_CODE: string }[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const set = (k: string, v: string) => setForm((p) => ({ ...p, [k]: v }));
|
||||
@@ -51,104 +51,83 @@ function UserForm() {
|
||||
finally { setLoading(false); }
|
||||
};
|
||||
|
||||
const isCustomer = form.user_type === "C";
|
||||
|
||||
return (
|
||||
<div className="p-5 bg-white min-h-screen">
|
||||
<h2 className="text-base font-bold text-gray-800 mb-4 border-b pb-2">
|
||||
<div className="p-3 bg-white min-h-screen text-[12px]">
|
||||
<h2 className="text-sm font-bold text-gray-800 mb-2 border-b pb-1.5">
|
||||
{isNew ? "사용자 등록" : `사용자 수정 (${userId})`}
|
||||
</h2>
|
||||
<div className="grid grid-cols-2 gap-x-4 gap-y-3 text-sm">
|
||||
<div><label className="block text-xs font-medium text-gray-500 mb-1">사용자 ID <span className="text-red-400">*</span></label>
|
||||
<Input value={form.user_id || ""} onChange={(e) => set("user_id", e.target.value)} readOnly={!isNew} /></div>
|
||||
<div><label className="block text-xs font-medium text-gray-500 mb-1">이름/업체명 <span className="text-red-400">*</span></label>
|
||||
<Input value={form.user_name || ""} onChange={(e) => set("user_name", e.target.value)} /></div>
|
||||
<div><label className="block text-xs font-medium text-gray-500 mb-1">사번</label>
|
||||
<Input value={form.sabun || ""} onChange={(e) => set("sabun", e.target.value)} /></div>
|
||||
<div><label className="block text-xs font-medium text-gray-500 mb-1">부서</label>
|
||||
<select value={form.dept_code || ""} onChange={(e) => { set("dept_code", e.target.value); const d = depts.find((x) => x.dept_code === e.target.value); if (d) set("dept_name", d.dept_name); }}
|
||||
className="h-9 w-full rounded border border-gray-300 bg-white px-2 text-sm">
|
||||
<div className="grid grid-cols-2 gap-x-3 gap-y-1.5">
|
||||
<div><label className="block text-[11px] font-medium text-gray-500 mb-0.5">사용자 ID <span className="text-red-400">*</span></label>
|
||||
<Input className="h-8" value={form.user_id || ""} onChange={(e) => set("user_id", e.target.value)} readOnly={!isNew} /></div>
|
||||
<div><label className="block text-[11px] font-medium text-gray-500 mb-0.5">이름/업체명 <span className="text-red-400">*</span></label>
|
||||
<Input className="h-8" value={form.user_name || ""} onChange={(e) => set("user_name", e.target.value)} /></div>
|
||||
<div><label className="block text-[11px] font-medium text-gray-500 mb-0.5">부서</label>
|
||||
<select value={form.dept_code || ""}
|
||||
onChange={(e) => { set("dept_code", e.target.value); const d = depts.find((x) => x.DEPT_CODE === e.target.value); if (d) set("dept_name", d.DEPT_NAME); }}
|
||||
className="h-8 w-full rounded border border-gray-300 bg-white px-2 text-[12px]">
|
||||
<option value="">선택</option>
|
||||
{depts.map((d) => <option key={d.dept_code} value={d.dept_code}>{d.dept_name}</option>)}
|
||||
{depts.map((d) => <option key={d.DEPT_CODE} value={d.DEPT_CODE}>{d.DEPT_NAME}</option>)}
|
||||
</select></div>
|
||||
<div><label className="block text-xs font-medium text-gray-500 mb-1">직급/직책</label>
|
||||
<Input value={form.position_name || ""} onChange={(e) => set("position_name", e.target.value)} /></div>
|
||||
<div><label className="block text-xs font-medium text-gray-500 mb-1">이메일</label>
|
||||
<Input type="email" value={form.email || ""} onChange={(e) => set("email", e.target.value)} /></div>
|
||||
<div><label className="block text-xs font-medium text-gray-500 mb-1">휴대폰</label>
|
||||
<Input value={form.cell_phone || ""} onChange={(e) => set("cell_phone", e.target.value)} /></div>
|
||||
<div><label className="block text-xs font-medium text-gray-500 mb-1">전화번호</label>
|
||||
<Input value={form.tel || ""} onChange={(e) => set("tel", e.target.value)} /></div>
|
||||
<div><label className="block text-xs font-medium text-gray-500 mb-1">유형</label>
|
||||
<div><label className="block text-[11px] font-medium text-gray-500 mb-0.5">유형</label>
|
||||
<select value={form.user_type || ""} onChange={(e) => set("user_type", e.target.value)}
|
||||
className="h-9 w-full rounded border border-gray-300 bg-white px-2 text-sm">
|
||||
className="h-8 w-full rounded border border-gray-300 bg-white px-2 text-[12px]">
|
||||
<option value="">선택</option>
|
||||
<option value="A">관리자</option>
|
||||
<option value="U">사용자</option>
|
||||
</select></div>
|
||||
<div><label className="block text-xs font-medium text-gray-500 mb-1">대표자</label>
|
||||
<Input value={form.ceo_name || ""} onChange={(e) => set("ceo_name", e.target.value)} /></div>
|
||||
<div className="col-span-2"><label className="block text-xs font-medium text-gray-500 mb-1">주소</label>
|
||||
<Input value={form.address || ""} onChange={(e) => set("address", e.target.value)} /></div>
|
||||
<div><label className="block text-xs font-medium text-gray-500 mb-1">사업자등록번호</label>
|
||||
<Input value={form.biz_no || ""} onChange={(e) => set("biz_no", e.target.value)} /></div>
|
||||
<div><label className="block text-[11px] font-medium text-gray-500 mb-0.5">직급/직책</label>
|
||||
<Input className="h-8" value={form.position_name || ""} onChange={(e) => set("position_name", e.target.value)} /></div>
|
||||
<div><label className="block text-[11px] font-medium text-gray-500 mb-0.5">이메일</label>
|
||||
<Input className="h-8" type="email" value={form.email || ""} onChange={(e) => set("email", e.target.value)} /></div>
|
||||
<div><label className="block text-[11px] font-medium text-gray-500 mb-0.5">휴대폰</label>
|
||||
<Input className="h-8" value={form.cell_phone || ""} onChange={(e) => set("cell_phone", e.target.value)} /></div>
|
||||
<div><label className="block text-[11px] font-medium text-gray-500 mb-0.5">전화번호</label>
|
||||
<Input className="h-8" value={form.tel || ""} onChange={(e) => set("tel", e.target.value)} /></div>
|
||||
<div><label className="block text-[11px] font-medium text-gray-500 mb-0.5">대표자</label>
|
||||
<Input className="h-8" value={form.ceo_name || ""} onChange={(e) => set("ceo_name", e.target.value)} /></div>
|
||||
<div><label className="block text-[11px] font-medium text-gray-500 mb-0.5">사업자등록번호</label>
|
||||
<Input className="h-8" value={form.biz_no || ""} onChange={(e) => set("biz_no", e.target.value)} /></div>
|
||||
<div className="col-span-2"><label className="block text-[11px] font-medium text-gray-500 mb-0.5">주소</label>
|
||||
<Input className="h-8" value={form.address || ""} onChange={(e) => set("address", e.target.value)} /></div>
|
||||
|
||||
{isNew && (
|
||||
<div className="col-span-2"><label className="block text-xs font-medium text-gray-500 mb-1">초기 비밀번호</label>
|
||||
<Input type="password" value={form.password || ""} onChange={(e) => set("password", e.target.value)} placeholder="비밀번호 입력 (미입력 시 1234)" /></div>
|
||||
<div className="col-span-2"><label className="block text-[11px] font-medium text-gray-500 mb-0.5">초기 비밀번호</label>
|
||||
<Input className="h-8" type="password" value={form.password || ""} onChange={(e) => set("password", e.target.value)} placeholder="비밀번호 입력 (미입력 시 1)" /></div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!isNew && (
|
||||
<div className="mt-5 pt-4 border-t">
|
||||
<h3 className="text-sm font-bold text-gray-700 mb-3">출고 기준 창고</h3>
|
||||
<p className="text-[11px] text-gray-500 mb-2">이 사용자의 발주 건이 출고될 때 재고가 차감되는 창고입니다. 미지정 시 기본 STOCK 창고.</p>
|
||||
<select
|
||||
value={form.default_wh_objid || ""}
|
||||
onChange={(e) => set("default_wh_objid", e.target.value)}
|
||||
className="h-9 w-full rounded border border-gray-300 bg-white px-2 text-sm"
|
||||
>
|
||||
<option value="">미지정 (기본 창고)</option>
|
||||
{whs.map((w) => (
|
||||
<option key={w.OBJID} value={w.OBJID}>{w.WH_NAME} ({w.WH_CODE})</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isNew && (
|
||||
<div className="mt-5 pt-4 border-t">
|
||||
<h3 className="text-sm font-bold text-gray-700 mb-3">특수 권한 (발주 시 적용)</h3>
|
||||
<div className="space-y-2">
|
||||
<label className="flex items-center gap-3 p-2.5 rounded border hover:bg-gray-50 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={form.unlimited_qty === "Y"}
|
||||
onChange={(e) => set("unlimited_qty", e.target.checked ? "Y" : "N")}
|
||||
className="w-4 h-4"
|
||||
/>
|
||||
<div>
|
||||
<div className="text-sm font-semibold text-gray-800">제한수량 해지</div>
|
||||
<div className="text-[11px] text-gray-500">품목별 1회 발주 제한수량(max_order_qty)을 무시하고 재고만큼 발주 가능</div>
|
||||
</div>
|
||||
</label>
|
||||
<label className="flex items-center gap-3 p-2.5 rounded border hover:bg-gray-50 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={form.view_hidden === "Y"}
|
||||
onChange={(e) => set("view_hidden", e.target.checked ? "Y" : "N")}
|
||||
className="w-4 h-4"
|
||||
/>
|
||||
<div>
|
||||
<div className="text-sm font-semibold text-gray-800">숨김처리 보기</div>
|
||||
<div className="text-[11px] text-gray-500">관리자가 숨김(is_hidden=Y)으로 등록한 품목도 발주 화면에 표시</div>
|
||||
</div>
|
||||
</label>
|
||||
<div className="mt-2 pt-2 border-t grid grid-cols-2 gap-x-3 gap-y-1.5">
|
||||
<div>
|
||||
<label className="block text-[11px] font-medium text-gray-500 mb-0.5">출고 기준 창고 <span className="text-gray-400 font-normal">(미지정 = 기본)</span></label>
|
||||
<select
|
||||
value={form.default_wh_objid || ""}
|
||||
onChange={(e) => set("default_wh_objid", e.target.value)}
|
||||
className="h-8 w-full rounded border border-gray-300 bg-white px-2 text-[12px]"
|
||||
>
|
||||
<option value="">미지정 (기본 창고)</option>
|
||||
{whs.map((w) => (
|
||||
<option key={w.OBJID} value={w.OBJID}>{w.WH_NAME} ({w.WH_CODE})</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-[11px] font-medium text-gray-500 mb-0.5">특수 권한</label>
|
||||
<div className="flex gap-2 h-8 items-center">
|
||||
<label className="inline-flex items-center gap-1 px-2 h-8 border rounded text-[11px] cursor-pointer hover:bg-gray-50">
|
||||
<input type="checkbox" checked={form.unlimited_qty === "Y"} onChange={(e) => set("unlimited_qty", e.target.checked ? "Y" : "N")} className="w-3.5 h-3.5" />
|
||||
발주한도 무시
|
||||
</label>
|
||||
<label className="inline-flex items-center gap-1 px-2 h-8 border rounded text-[11px] cursor-pointer hover:bg-gray-50">
|
||||
<input type="checkbox" checked={form.view_hidden === "Y"} onChange={(e) => set("view_hidden", e.target.checked ? "Y" : "N")} className="w-3.5 h-3.5" />
|
||||
숨김품목 보기
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex justify-end gap-2 mt-6 pt-4 border-t">
|
||||
<div className="flex justify-end gap-2 mt-3 pt-2 border-t">
|
||||
<Button onClick={handleSave} disabled={loading}>{loading ? "저장 중..." : "저장"}</Button>
|
||||
<Button variant="secondary" onClick={() => window.close()}>닫기</Button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user