Files
distribution_erp/src/store/menu-store.ts
T
chpark 6cfe0041a2
Deploy momo-erp / deploy (push) Successful in 53s
feat: 매입 발주서 납품조건 + 공급업체 샘플 + 관리자/사용자 모드 토글 + 모달 sticky
[DB 019]
- momo_procurements 에 delivery_place / delivery_period / payment_terms / freight_terms 컬럼 추가
- 기존 supply_mng (공급업체) 데이터 모두 삭제 + 샘플 10개 신규 등록
  · (주)아바텍, 대성식품, (주)고기파는농부, 광이진천 농장, 단과일,
    봉담수산, 명일동유기농, 울산단과일, 농부의아침, 초록마을 도매
- 시퀀스 가정 없이 MAX(objid)+1 로 안전하게 부여

[발주서 양식 — 표준 거래명세표 양식 반영]
- ProcurementForm: "2. 납품조건" 섹션 추가
  · 1)~3) 표준 조항 (납기 지연 공제 / 검수 부적합 반출 / 수량 규격 변경)
  · 4) 납품장소 5) 납품기간 6) 대금지불 7) 운임부담 — 표 형식 입력칸
  · 8)~9) 표준 조항 (3일 이의 제기 효력 / 명시되지 않은 사항)
  · 하단 "상기와 같이 발주함." + 발주일 + 발주자
- update-header API: 4개 필드 동적 업데이트
- /api/m/procurements/excel/[id]: 엑셀 출력에도 납품조건 9개 항목 + 4필드 표
- /api/m/procurements/send: 메일 본문 HTML 에도 납품조건 표 + 표준 조항

[관리자/사용자 모드 토글]
- 헤더 매뉴얼 옆에 [👥 사용자 / 🛡 관리자] 토글 버튼 (admin 권한자만 노출)
- menu-store: viewMode("user"|"admin") + setViewMode 추가
- 사이드바: viewMode 에 따라 대메뉴 필터링
  · 사용자 모드: '거래처 주문' 그룹만
  · 관리자 모드: 출고/정산 + 매입/입고 + 마스터 관리 + 통계
- admin 권한자 자동으로 로그인 시 관리자 모드 진입

[ItemPicker 모달 모바일 친화]
- 모바일에서 화면 하단 도킹(items-end) → 풀스크린 시트 처럼
- 헤더는 sticky top-0 으로 고정 → 긴 목록에서도 검색바 항상 보임

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 11:42:45 +09:00

80 lines
2.7 KiB
TypeScript

import { create } from "zustand";
import type { MenuItem } from "@/types";
interface MenuState {
topMenus: { OBJID: string; MENU_NAME_KOR: string }[];
sideMenus: MenuItem[];
activeTopMenu: string;
activeSubMenu: string;
isCollapsed: boolean;
/** 모바일에서 사이드바 오버레이로 펼침 여부 */
mobileOpen: boolean;
/** 관리자 모드 / 사용자 모드 — 사이드바에 보일 메뉴 그룹 결정 */
viewMode: "user" | "admin";
setTopMenus: (menus: { OBJID: string; MENU_NAME_KOR: string }[]) => void;
setSideMenus: (menus: MenuItem[]) => void;
setActiveTopMenu: (id: string) => void;
setActiveSubMenu: (id: string) => void;
toggleCollapsed: () => void;
setMobileOpen: (v: boolean) => void;
setViewMode: (v: "user" | "admin") => void;
fetchTopMenus: () => Promise<void>;
fetchSideMenus: (menuObjId: string) => Promise<void>;
}
export const useMenuStore = create<MenuState>((set) => ({
topMenus: [],
sideMenus: [],
activeTopMenu: "",
activeSubMenu: "",
isCollapsed: false,
mobileOpen: false,
viewMode: "user",
setTopMenus: (topMenus) => set({ topMenus }),
setSideMenus: (sideMenus) => set({ sideMenus }),
setActiveTopMenu: (activeTopMenu) => set({ activeTopMenu }),
setActiveSubMenu: (activeSubMenu) => set({ activeSubMenu }),
toggleCollapsed: () => set((s) => ({ isCollapsed: !s.isCollapsed })),
setMobileOpen: (v) => set({ mobileOpen: v }),
setViewMode: (v) => set({ viewMode: v }),
fetchTopMenus: async () => {
const res = await fetch("/api/menu/top");
if (res.ok) {
const data = await res.json();
set({ topMenus: data.menus || [] });
}
},
fetchSideMenus: async (menuObjId: string) => {
const res = await fetch("/api/menu", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ MENUOBJID: menuObjId }),
});
if (res.ok) {
const data = await res.json();
const items = (data.RESULT || []) as MenuItem[];
// 트리 구조 빌드 (menu.jsp add_menu 로직 대응)
const parents = items.filter((i) => i.level === "1" || Number(i.level) < 2);
const children = items.filter((i) => i.level !== "1" && Number(i.level) >= 2);
const tree = parents.map((p) => {
const kids = children.filter((c) => c.parentObjId === p.objid);
// 동일 메뉴명 중복 제거 (SEQ 순서 유지, 첫 번째만 유지)
const seen = new Set<string>();
const uniqueKids = kids.filter((c) => {
if (seen.has(c.menuNameKor)) return false;
seen.add(c.menuNameKor);
return true;
});
return { ...p, children: uniqueKids };
});
set({ sideMenus: tree, activeTopMenu: menuObjId });
}
},
}));