6cfe0041a2
Deploy momo-erp / deploy (push) Successful in 53s
[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>
80 lines
2.7 KiB
TypeScript
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 });
|
|
}
|
|
},
|
|
}));
|