feat: add item list mode configuration and screen code handling

- Introduced `itemListMode` to the process work standard configuration, allowing users to select between displaying all items or only registered items.
- Added `screenCode` to automatically set the screen ID when in registered mode.
- Updated the `ProcessWorkStandardComponent` to handle the new configuration and adjust item fetching logic accordingly.
- Enhanced the `ProcessWorkStandardConfigPanel` to include a select input for item list mode, improving user experience and configurability.

These changes aim to enhance the flexibility and usability of the process work standard component.

Made-with: Cursor
This commit is contained in:
kjs 2026-03-13 14:01:09 +09:00
parent bd08b341f0
commit 429f1ba6ee
6 changed files with 111 additions and 13 deletions

View File

@ -17,25 +17,37 @@ interface ProcessWorkStandardComponentProps {
formData?: Record<string, any>;
isPreview?: boolean;
tableName?: string;
screenId?: number | string;
}
export function ProcessWorkStandardComponent({
config: configProp,
isPreview,
screenId,
}: ProcessWorkStandardComponentProps) {
const resolvedConfig = useMemo(() => {
const merged = {
...configProp,
};
if (merged.itemListMode === "registered" && !merged.screenCode && screenId) {
merged.screenCode = `screen_${screenId}`;
}
return merged;
}, [configProp, screenId]);
const config: ProcessWorkStandardConfig = useMemo(
() => ({
...defaultConfig,
...configProp,
dataSource: { ...defaultConfig.dataSource, ...configProp?.dataSource },
phases: configProp?.phases?.length
? configProp.phases
...resolvedConfig,
dataSource: { ...defaultConfig.dataSource, ...resolvedConfig?.dataSource },
phases: resolvedConfig?.phases?.length
? resolvedConfig.phases
: defaultConfig.phases,
detailTypes: configProp?.detailTypes?.length
? configProp.detailTypes
detailTypes: resolvedConfig?.detailTypes?.length
? resolvedConfig.detailTypes
: defaultConfig.detailTypes,
}),
[configProp]
[resolvedConfig]
);
const {
@ -46,7 +58,8 @@ export function ProcessWorkStandardComponent({
selectedDetailsByPhase,
selection,
loading,
fetchItems,
isRegisteredMode,
loadItems,
selectItem,
selectProcess,
fetchWorkItemDetails,
@ -112,8 +125,8 @@ export function ProcessWorkStandardComponent({
);
const handleInit = useCallback(() => {
fetchItems();
}, [fetchItems]);
loadItems();
}, [loadItems]);
const splitRatio = config.splitRatio || 30;
@ -144,7 +157,7 @@ export function ProcessWorkStandardComponent({
items={items}
routings={routings}
selection={selection}
onSearch={(keyword) => fetchItems(keyword)}
onSearch={(keyword) => loadItems(keyword)}
onSelectItem={selectItem}
onSelectProcess={selectProcess}
onInit={handleInit}

View File

@ -6,6 +6,7 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { ProcessWorkStandardConfig, WorkPhaseDefinition, DetailTypeDefinition } from "./types";
import { defaultConfig } from "./config";
@ -81,6 +82,30 @@ export function ProcessWorkStandardConfigPanel({
<div className="space-y-5 p-4">
<h3 className="text-sm font-semibold"> </h3>
{/* 품목 목록 모드 */}
<section className="space-y-3">
<p className="text-xs font-medium text-muted-foreground"> </p>
<div>
<Select
value={config.itemListMode || "all"}
onValueChange={(v) => update({ itemListMode: v as "all" | "registered" })}
>
<SelectTrigger className="h-8 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="all"> </SelectItem>
<SelectItem value="registered"> </SelectItem>
</SelectContent>
</Select>
<p className="mt-1 text-[10px] text-muted-foreground">
{config.itemListMode === "registered"
? "품목별 라우팅 탭에서 등록한 품목만 표시됩니다. screenCode는 화면 ID 기준으로 자동 설정됩니다."
: "모든 품목을 표시합니다."}
</p>
</div>
</section>
{/* 데이터 소스 설정 */}
<section className="space-y-3">
<p className="text-xs font-medium text-muted-foreground"> </p>

View File

@ -9,7 +9,7 @@ export class ProcessWorkStandardRenderer extends AutoRegisteringComponentRendere
static componentDefinition = V2ProcessWorkStandardDefinition;
render(): React.ReactElement {
const { formData, isPreview, config, tableName } = this.props as Record<
const { formData, isPreview, config, tableName, screenId } = this.props as Record<
string,
unknown
>;
@ -20,6 +20,7 @@ export class ProcessWorkStandardRenderer extends AutoRegisteringComponentRendere
formData={formData as Record<string, unknown>}
tableName={tableName as string}
isPreview={isPreview as boolean}
screenId={screenId as number | string}
/>
);
}

View File

@ -31,4 +31,6 @@ export const defaultConfig: ProcessWorkStandardConfig = {
splitRatio: 30,
leftPanelTitle: "품목 및 공정 선택",
readonly: false,
itemListMode: "all",
screenCode: "",
};

View File

@ -32,7 +32,9 @@ export function useProcessWorkStandard(config: ProcessWorkStandardConfig) {
processName: null,
});
// 품목 목록 조회
const isRegisteredMode = config.itemListMode === "registered";
// 품목 목록 조회 (전체 모드)
const fetchItems = useCallback(
async (search?: string) => {
try {
@ -59,6 +61,53 @@ export function useProcessWorkStandard(config: ProcessWorkStandardConfig) {
[config.dataSource]
);
// 등록 품목 조회 (등록 모드)
const fetchRegisteredItems = useCallback(
async (search?: string) => {
const screenCode = config.screenCode;
if (!screenCode) {
console.warn("screenCode가 설정되지 않았습니다");
setItems([]);
return;
}
try {
setLoading(true);
const ds = config.dataSource;
const params = new URLSearchParams({
tableName: ds.itemTable,
nameColumn: ds.itemNameColumn,
codeColumn: ds.itemCodeColumn,
routingTable: ds.routingVersionTable,
routingFkColumn: ds.routingFkColumn,
...(search ? { search } : {}),
});
const res = await apiClient.get(
`${API_BASE}/registered-items/${encodeURIComponent(screenCode)}?${params}`
);
if (res.data?.success) {
setItems(res.data.data || []);
}
} catch (err) {
console.error("등록 품목 조회 실패", err);
} finally {
setLoading(false);
}
},
[config.dataSource, config.screenCode]
);
// 모드에 따라 적절한 함수 호출
const loadItems = useCallback(
async (search?: string) => {
if (isRegisteredMode) {
await fetchRegisteredItems(search);
} else {
await fetchItems(search);
}
},
[isRegisteredMode, fetchItems, fetchRegisteredItems]
);
// 라우팅 + 공정 조회
const fetchRoutings = useCallback(
async (itemCode: string) => {
@ -340,7 +389,10 @@ export function useProcessWorkStandard(config: ProcessWorkStandardConfig) {
selection,
loading,
saving,
isRegisteredMode,
fetchItems,
fetchRegisteredItems,
loadItems,
selectItem,
selectProcess,
fetchWorkItems,

View File

@ -37,6 +37,10 @@ export interface ProcessWorkStandardConfig {
splitRatio?: number;
leftPanelTitle?: string;
readonly?: boolean;
/** 품목 목록 모드: all=전체, registered=등록된 품목만 */
itemListMode?: "all" | "registered";
/** 등록 모드 시 화면 코드 (자동 설정됨) */
screenCode?: string;
}
// ============================================================
@ -121,6 +125,7 @@ export interface ProcessWorkStandardComponentProps {
formData?: Record<string, any>;
isPreview?: boolean;
tableName?: string;
screenId?: number | string;
}
// 선택 상태