feat(WEB): 자재/출고/생산/품질/단가 기능 대폭 개선 및 신규 페이지 추가
자재관리: - 입고관리 재고조정 다이얼로그 신규 추가, 상세/목록 기능 확장 - 재고현황 컴포넌트 리팩토링 출고관리: - 출하관리 생성/수정/목록/상세 개선 - 차량배차관리 상세/수정/목록 기능 보강 생산관리: - 작업지시서 WIP 생산 모달 신규 추가 - 벤딩WIP/슬랫조인트바 검사 콘텐츠 신규 추가 - 작업자화면 기능 대폭 확장 (카드/목록 개선) - 검사성적서 모달 개선 품질관리: - 실적보고서 관리 페이지 신규 추가 - 검사관리 문서/타입/목데이터 개선 단가관리: - 단가배포 페이지 및 컴포넌트 신규 추가 - 단가표 관리 페이지 및 컴포넌트 신규 추가 공통: - 권한 시스템 추가 개선 (PermissionContext, usePermission, PermissionGuard) - 메뉴 폴링 훅 개선, 레이아웃 수정 - 모바일 줌/패닝 CSS 수정 - locale 유틸 추가 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
export type PermissionAction = 'view' | 'create' | 'update' | 'delete' | 'approve' | 'export';
|
||||
|
||||
export const PERMISSION_ACTIONS: PermissionAction[] =
|
||||
['view', 'create', 'update', 'delete', 'approve', 'export'];
|
||||
|
||||
/** flat 변환된 권한 맵 (프론트엔드 사용) */
|
||||
export interface PermissionMap {
|
||||
[url: string]: {
|
||||
@@ -18,3 +21,14 @@ export interface UsePermissionReturn {
|
||||
isLoading: boolean;
|
||||
matchedUrl: string | null;
|
||||
}
|
||||
|
||||
export const ALL_ALLOWED: UsePermissionReturn = {
|
||||
canView: true, canCreate: true, canUpdate: true,
|
||||
canDelete: true, canApprove: true, canExport: true,
|
||||
isLoading: false, matchedUrl: null,
|
||||
};
|
||||
|
||||
export const ALL_DENIED_PERMS: Record<PermissionAction, false> = {
|
||||
view: false, create: false, update: false,
|
||||
delete: false, approve: false, export: false,
|
||||
};
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import type { PermissionMap, PermissionAction } from './types';
|
||||
import { PERMISSION_ACTIONS } from './types';
|
||||
import type { PermissionMap } from './types';
|
||||
import { stripLocalePrefix } from '@/lib/utils/locale';
|
||||
|
||||
interface SerializableMenuItem {
|
||||
id: string;
|
||||
@@ -38,14 +40,13 @@ export function convertMatrixToPermissionMap(
|
||||
menuIdToUrl: Record<string, string>
|
||||
): PermissionMap {
|
||||
const map: PermissionMap = {};
|
||||
const actions: PermissionAction[] = ['view', 'create', 'update', 'delete', 'approve', 'export'];
|
||||
|
||||
for (const [menuId, perms] of Object.entries(permissions)) {
|
||||
const url = menuIdToUrl[menuId];
|
||||
if (!url) continue; // URL 매핑 없는 메뉴 스킵
|
||||
|
||||
map[url] = {};
|
||||
for (const action of actions) {
|
||||
for (const action of PERMISSION_ACTIONS) {
|
||||
// API는 허용된 권한만 포함, 누락된 action = 비허용(false)
|
||||
map[url][action] = perms[action] === true;
|
||||
}
|
||||
@@ -66,8 +67,7 @@ export function mergePermissionMaps(maps: PermissionMap[]): PermissionMap {
|
||||
|
||||
for (const url of allUrls) {
|
||||
merged[url] = {};
|
||||
const actions: PermissionAction[] = ['view', 'create', 'update', 'delete', 'approve', 'export'];
|
||||
for (const action of actions) {
|
||||
for (const action of PERMISSION_ACTIONS) {
|
||||
const values = maps
|
||||
.map(m => m[url]?.[action])
|
||||
.filter((v): v is boolean => v !== undefined);
|
||||
@@ -84,7 +84,7 @@ export function mergePermissionMaps(maps: PermissionMap[]): PermissionMap {
|
||||
* Longest prefix match: 현재 경로에서 가장 길게 매칭되는 권한 URL 찾기
|
||||
*/
|
||||
export function findMatchingUrl(currentPath: string, permissionMap: PermissionMap): string | null {
|
||||
const pathWithoutLocale = currentPath.replace(/^\/(ko|en|ja)(\/|$)/, '/');
|
||||
const pathWithoutLocale = stripLocalePrefix(currentPath);
|
||||
|
||||
if (permissionMap[pathWithoutLocale]) {
|
||||
return pathWithoutLocale;
|
||||
@@ -100,12 +100,3 @@ export function findMatchingUrl(currentPath: string, permissionMap: PermissionMa
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* CRUD 라우트에서 현재 액션 추론
|
||||
*/
|
||||
export function inferActionFromPath(path: string): PermissionAction {
|
||||
if (path.endsWith('/new') || path.endsWith('/create')) return 'create';
|
||||
if (path.endsWith('/edit')) return 'update';
|
||||
return 'view';
|
||||
}
|
||||
|
||||
14
src/lib/utils/locale.ts
Normal file
14
src/lib/utils/locale.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { locales } from '@/i18n/config';
|
||||
|
||||
const LOCALE_PREFIX_RE = new RegExp(`^\\/(${locales.join('|')})(\/|$)`);
|
||||
const LOCALE_PREFIX_SLASH_RE = new RegExp(`^\\/(${locales.join('|')})\\/`);
|
||||
|
||||
/** URL에서 locale prefix 제거 (/ko/hr/... → /hr/...) */
|
||||
export function stripLocalePrefix(path: string): string {
|
||||
return path.replace(LOCALE_PREFIX_RE, '/');
|
||||
}
|
||||
|
||||
/** path 내부의 locale prefix만 제거 (슬래시 필수) */
|
||||
export function stripLocaleSlashPrefix(path: string): string {
|
||||
return path.replace(LOCALE_PREFIX_SLASH_RE, '/');
|
||||
}
|
||||
Reference in New Issue
Block a user