refactor: Update ProcessWorkStandard component to manage work item selection by phase
- Removed the "정보조회" option from the default configuration. - Refactored the ProcessWorkStandardComponent to handle work item selection independently for each phase. - Updated the WorkPhaseSection to pass phase-specific parameters for work item selection and detail management. - Enhanced the useProcessWorkStandard hook to maintain separate states for selected work items and details by phase, improving data handling and user experience.
This commit is contained in:
parent
2335a413cb
commit
38ade7562e
|
|
@ -42,8 +42,8 @@ export function ProcessWorkStandardComponent({
|
||||||
items,
|
items,
|
||||||
routings,
|
routings,
|
||||||
workItems,
|
workItems,
|
||||||
selectedWorkItemDetails,
|
selectedWorkItemIdByPhase,
|
||||||
selectedWorkItemId,
|
selectedDetailsByPhase,
|
||||||
selection,
|
selection,
|
||||||
loading,
|
loading,
|
||||||
fetchItems,
|
fetchItems,
|
||||||
|
|
@ -105,8 +105,8 @@ export function ProcessWorkStandardComponent({
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSelectWorkItem = useCallback(
|
const handleSelectWorkItem = useCallback(
|
||||||
(workItemId: string) => {
|
(workItemId: string, phaseKey: string) => {
|
||||||
fetchWorkItemDetails(workItemId);
|
fetchWorkItemDetails(workItemId, phaseKey);
|
||||||
},
|
},
|
||||||
[fetchWorkItemDetails]
|
[fetchWorkItemDetails]
|
||||||
);
|
);
|
||||||
|
|
@ -191,8 +191,8 @@ export function ProcessWorkStandardComponent({
|
||||||
key={phase.key}
|
key={phase.key}
|
||||||
phase={phase}
|
phase={phase}
|
||||||
items={workItemsByPhase[phase.key] || []}
|
items={workItemsByPhase[phase.key] || []}
|
||||||
selectedWorkItemId={selectedWorkItemId}
|
selectedWorkItemId={selectedWorkItemIdByPhase[phase.key] || null}
|
||||||
selectedWorkItemDetails={selectedWorkItemDetails}
|
selectedWorkItemDetails={selectedDetailsByPhase[phase.key] || []}
|
||||||
detailTypes={config.detailTypes}
|
detailTypes={config.detailTypes}
|
||||||
readonly={config.readonly}
|
readonly={config.readonly}
|
||||||
onSelectWorkItem={handleSelectWorkItem}
|
onSelectWorkItem={handleSelectWorkItem}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useState } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { Plus, Trash2 } from "lucide-react";
|
import { Plus, Trash2 } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
|
@ -61,11 +61,24 @@ export function WorkItemAddModal({
|
||||||
detailTypes,
|
detailTypes,
|
||||||
editItem,
|
editItem,
|
||||||
}: WorkItemAddModalProps) {
|
}: WorkItemAddModalProps) {
|
||||||
const [title, setTitle] = useState(editItem?.title || "");
|
const [title, setTitle] = useState("");
|
||||||
const [isRequired, setIsRequired] = useState(editItem?.is_required || "Y");
|
const [isRequired, setIsRequired] = useState("Y");
|
||||||
const [description, setDescription] = useState(editItem?.description || "");
|
const [description, setDescription] = useState("");
|
||||||
const [details, setDetails] = useState<ModalDetail[]>([]);
|
const [details, setDetails] = useState<ModalDetail[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open && editItem) {
|
||||||
|
setTitle(editItem.title || "");
|
||||||
|
setIsRequired(editItem.is_required || "Y");
|
||||||
|
setDescription(editItem.description || "");
|
||||||
|
} else if (open && !editItem) {
|
||||||
|
setTitle("");
|
||||||
|
setIsRequired("Y");
|
||||||
|
setDescription("");
|
||||||
|
setDetails([]);
|
||||||
|
}
|
||||||
|
}, [open, editItem]);
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
setTitle("");
|
setTitle("");
|
||||||
setIsRequired("Y");
|
setIsRequired("Y");
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,13 @@ interface WorkPhaseSectionProps {
|
||||||
selectedWorkItemDetails: WorkItemDetail[];
|
selectedWorkItemDetails: WorkItemDetail[];
|
||||||
detailTypes: DetailTypeDefinition[];
|
detailTypes: DetailTypeDefinition[];
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
onSelectWorkItem: (workItemId: string) => void;
|
onSelectWorkItem: (workItemId: string, phaseKey: string) => void;
|
||||||
onAddWorkItem: (phase: string) => void;
|
onAddWorkItem: (phase: string) => void;
|
||||||
onEditWorkItem: (item: WorkItem) => void;
|
onEditWorkItem: (item: WorkItem) => void;
|
||||||
onDeleteWorkItem: (id: string) => void;
|
onDeleteWorkItem: (id: string) => void;
|
||||||
onCreateDetail: (workItemId: string, data: Partial<WorkItemDetail>) => void;
|
onCreateDetail: (workItemId: string, data: Partial<WorkItemDetail>, phaseKey: string) => void;
|
||||||
onUpdateDetail: (id: string, data: Partial<WorkItemDetail>) => void;
|
onUpdateDetail: (id: string, data: Partial<WorkItemDetail>, phaseKey: string) => void;
|
||||||
onDeleteDetail: (id: string) => void;
|
onDeleteDetail: (id: string, phaseKey: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function WorkPhaseSection({
|
export function WorkPhaseSection({
|
||||||
|
|
@ -45,9 +45,6 @@ export function WorkPhaseSection({
|
||||||
onDeleteDetail,
|
onDeleteDetail,
|
||||||
}: WorkPhaseSectionProps) {
|
}: WorkPhaseSectionProps) {
|
||||||
const selectedItem = items.find((i) => i.id === selectedWorkItemId) || null;
|
const selectedItem = items.find((i) => i.id === selectedWorkItemId) || null;
|
||||||
const isThisSectionSelected = items.some(
|
|
||||||
(i) => i.id === selectedWorkItemId
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-lg border bg-card">
|
<div className="rounded-lg border bg-card">
|
||||||
|
|
@ -94,7 +91,7 @@ export function WorkPhaseSection({
|
||||||
item={item}
|
item={item}
|
||||||
isSelected={selectedWorkItemId === item.id}
|
isSelected={selectedWorkItemId === item.id}
|
||||||
readonly={readonly}
|
readonly={readonly}
|
||||||
onClick={() => onSelectWorkItem(item.id)}
|
onClick={() => onSelectWorkItem(item.id, phase.key)}
|
||||||
onEdit={() => onEditWorkItem(item)}
|
onEdit={() => onEditWorkItem(item)}
|
||||||
onDelete={() => onDeleteWorkItem(item.id)}
|
onDelete={() => onDeleteWorkItem(item.id)}
|
||||||
/>
|
/>
|
||||||
|
|
@ -106,15 +103,15 @@ export function WorkPhaseSection({
|
||||||
{/* 우측: 상세 리스트 */}
|
{/* 우측: 상세 리스트 */}
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<WorkItemDetailList
|
<WorkItemDetailList
|
||||||
workItem={isThisSectionSelected ? selectedItem : null}
|
workItem={selectedItem}
|
||||||
details={isThisSectionSelected ? selectedWorkItemDetails : []}
|
details={selectedWorkItemDetails}
|
||||||
detailTypes={detailTypes}
|
detailTypes={detailTypes}
|
||||||
readonly={readonly}
|
readonly={readonly}
|
||||||
onCreateDetail={(data) =>
|
onCreateDetail={(data) =>
|
||||||
selectedWorkItemId && onCreateDetail(selectedWorkItemId, data)
|
selectedWorkItemId && onCreateDetail(selectedWorkItemId, data, phase.key)
|
||||||
}
|
}
|
||||||
onUpdateDetail={onUpdateDetail}
|
onUpdateDetail={(id, data) => onUpdateDetail(id, data, phase.key)}
|
||||||
onDeleteDetail={onDeleteDetail}
|
onDeleteDetail={(id) => onDeleteDetail(id, phase.key)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ export const defaultConfig: ProcessWorkStandardConfig = {
|
||||||
{ value: "inspect", label: "검사항목" },
|
{ value: "inspect", label: "검사항목" },
|
||||||
{ value: "procedure", label: "작업절차" },
|
{ value: "procedure", label: "작업절차" },
|
||||||
{ value: "input", label: "직접입력" },
|
{ value: "input", label: "직접입력" },
|
||||||
{ value: "info", label: "정보조회" },
|
|
||||||
],
|
],
|
||||||
splitRatio: 30,
|
splitRatio: 30,
|
||||||
leftPanelTitle: "품목 및 공정 선택",
|
leftPanelTitle: "품목 및 공정 선택",
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,9 @@ export function useProcessWorkStandard(config: ProcessWorkStandardConfig) {
|
||||||
const [items, setItems] = useState<ItemData[]>([]);
|
const [items, setItems] = useState<ItemData[]>([]);
|
||||||
const [routings, setRoutings] = useState<RoutingVersion[]>([]);
|
const [routings, setRoutings] = useState<RoutingVersion[]>([]);
|
||||||
const [workItems, setWorkItems] = useState<WorkItem[]>([]);
|
const [workItems, setWorkItems] = useState<WorkItem[]>([]);
|
||||||
const [selectedWorkItemDetails, setSelectedWorkItemDetails] = useState<WorkItemDetail[]>([]);
|
// 섹션(phase)별 독립적인 선택 상태 관리
|
||||||
const [selectedWorkItemId, setSelectedWorkItemId] = useState<string | null>(null);
|
const [selectedWorkItemIdByPhase, setSelectedWorkItemIdByPhase] = useState<Record<string, string | null>>({});
|
||||||
|
const [selectedDetailsByPhase, setSelectedDetailsByPhase] = useState<Record<string, WorkItemDetail[]>>({});
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
|
|
||||||
|
|
@ -101,15 +102,15 @@ export function useProcessWorkStandard(config: ProcessWorkStandardConfig) {
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 작업 항목 상세 조회
|
// 작업 항목 상세 조회 (phase별 독립 저장)
|
||||||
const fetchWorkItemDetails = useCallback(async (workItemId: string) => {
|
const fetchWorkItemDetails = useCallback(async (workItemId: string, phaseKey: string) => {
|
||||||
try {
|
try {
|
||||||
const res = await apiClient.get(
|
const res = await apiClient.get(
|
||||||
`${API_BASE}/work-items/${workItemId}/details`
|
`${API_BASE}/work-items/${workItemId}/details`
|
||||||
);
|
);
|
||||||
if (res.data?.success) {
|
if (res.data?.success) {
|
||||||
setSelectedWorkItemDetails(res.data.data);
|
setSelectedDetailsByPhase(prev => ({ ...prev, [phaseKey]: res.data.data }));
|
||||||
setSelectedWorkItemId(workItemId);
|
setSelectedWorkItemIdByPhase(prev => ({ ...prev, [phaseKey]: workItemId }));
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("상세 조회 실패", err);
|
console.error("상세 조회 실패", err);
|
||||||
|
|
@ -129,8 +130,8 @@ export function useProcessWorkStandard(config: ProcessWorkStandardConfig) {
|
||||||
processName: null,
|
processName: null,
|
||||||
}));
|
}));
|
||||||
setWorkItems([]);
|
setWorkItems([]);
|
||||||
setSelectedWorkItemDetails([]);
|
setSelectedDetailsByPhase({});
|
||||||
setSelectedWorkItemId(null);
|
setSelectedWorkItemIdByPhase({});
|
||||||
await fetchRoutings(itemCode);
|
await fetchRoutings(itemCode);
|
||||||
},
|
},
|
||||||
[fetchRoutings]
|
[fetchRoutings]
|
||||||
|
|
@ -151,8 +152,8 @@ export function useProcessWorkStandard(config: ProcessWorkStandardConfig) {
|
||||||
routingDetailId,
|
routingDetailId,
|
||||||
processName,
|
processName,
|
||||||
}));
|
}));
|
||||||
setSelectedWorkItemDetails([]);
|
setSelectedDetailsByPhase({});
|
||||||
setSelectedWorkItemId(null);
|
setSelectedWorkItemIdByPhase({});
|
||||||
await fetchWorkItems(routingDetailId);
|
await fetchWorkItems(routingDetailId);
|
||||||
},
|
},
|
||||||
[fetchWorkItems]
|
[fetchWorkItems]
|
||||||
|
|
@ -233,28 +234,43 @@ export function useProcessWorkStandard(config: ProcessWorkStandardConfig) {
|
||||||
const res = await apiClient.delete(`${API_BASE}/work-items/${id}`);
|
const res = await apiClient.delete(`${API_BASE}/work-items/${id}`);
|
||||||
if (res.data?.success && selection.routingDetailId) {
|
if (res.data?.success && selection.routingDetailId) {
|
||||||
await fetchWorkItems(selection.routingDetailId);
|
await fetchWorkItems(selection.routingDetailId);
|
||||||
if (selectedWorkItemId === id) {
|
// 삭제된 항목이 선택되어 있던 phase의 선택 상태 초기화
|
||||||
setSelectedWorkItemDetails([]);
|
setSelectedWorkItemIdByPhase(prev => {
|
||||||
setSelectedWorkItemId(null);
|
const next = { ...prev };
|
||||||
}
|
for (const phaseKey of Object.keys(next)) {
|
||||||
|
if (next[phaseKey] === id) {
|
||||||
|
next[phaseKey] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
setSelectedDetailsByPhase(prev => {
|
||||||
|
const next = { ...prev };
|
||||||
|
for (const phaseKey of Object.keys(next)) {
|
||||||
|
if (selectedWorkItemIdByPhase[phaseKey] === id) {
|
||||||
|
next[phaseKey] = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("작업 항목 삭제 실패", err);
|
console.error("작업 항목 삭제 실패", err);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[selection.routingDetailId, selectedWorkItemId, fetchWorkItems]
|
[selection.routingDetailId, selectedWorkItemIdByPhase, fetchWorkItems]
|
||||||
);
|
);
|
||||||
|
|
||||||
// 상세 추가
|
// 상세 추가
|
||||||
const createDetail = useCallback(
|
const createDetail = useCallback(
|
||||||
async (workItemId: string, data: Partial<WorkItemDetail>) => {
|
async (workItemId: string, data: Partial<WorkItemDetail>, phaseKey: string) => {
|
||||||
try {
|
try {
|
||||||
const res = await apiClient.post(`${API_BASE}/work-item-details`, {
|
const res = await apiClient.post(`${API_BASE}/work-item-details`, {
|
||||||
work_item_id: workItemId,
|
work_item_id: workItemId,
|
||||||
...data,
|
...data,
|
||||||
});
|
});
|
||||||
if (res.data?.success) {
|
if (res.data?.success) {
|
||||||
await fetchWorkItemDetails(workItemId);
|
await fetchWorkItemDetails(workItemId, phaseKey);
|
||||||
if (selection.routingDetailId) {
|
if (selection.routingDetailId) {
|
||||||
await fetchWorkItems(selection.routingDetailId);
|
await fetchWorkItems(selection.routingDetailId);
|
||||||
}
|
}
|
||||||
|
|
@ -268,32 +284,36 @@ export function useProcessWorkStandard(config: ProcessWorkStandardConfig) {
|
||||||
|
|
||||||
// 상세 수정
|
// 상세 수정
|
||||||
const updateDetail = useCallback(
|
const updateDetail = useCallback(
|
||||||
async (id: string, data: Partial<WorkItemDetail>) => {
|
async (id: string, data: Partial<WorkItemDetail>, phaseKey: string) => {
|
||||||
try {
|
try {
|
||||||
const res = await apiClient.put(
|
const res = await apiClient.put(
|
||||||
`${API_BASE}/work-item-details/${id}`,
|
`${API_BASE}/work-item-details/${id}`,
|
||||||
data
|
data
|
||||||
);
|
);
|
||||||
if (res.data?.success && selectedWorkItemId) {
|
if (res.data?.success) {
|
||||||
await fetchWorkItemDetails(selectedWorkItemId);
|
const workItemId = selectedWorkItemIdByPhase[phaseKey];
|
||||||
|
if (workItemId) {
|
||||||
|
await fetchWorkItemDetails(workItemId, phaseKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("상세 수정 실패", err);
|
console.error("상세 수정 실패", err);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[selectedWorkItemId, fetchWorkItemDetails]
|
[selectedWorkItemIdByPhase, fetchWorkItemDetails]
|
||||||
);
|
);
|
||||||
|
|
||||||
// 상세 삭제
|
// 상세 삭제
|
||||||
const deleteDetail = useCallback(
|
const deleteDetail = useCallback(
|
||||||
async (id: string) => {
|
async (id: string, phaseKey: string) => {
|
||||||
try {
|
try {
|
||||||
const res = await apiClient.delete(
|
const res = await apiClient.delete(
|
||||||
`${API_BASE}/work-item-details/${id}`
|
`${API_BASE}/work-item-details/${id}`
|
||||||
);
|
);
|
||||||
if (res.data?.success) {
|
if (res.data?.success) {
|
||||||
if (selectedWorkItemId) {
|
const workItemId = selectedWorkItemIdByPhase[phaseKey];
|
||||||
await fetchWorkItemDetails(selectedWorkItemId);
|
if (workItemId) {
|
||||||
|
await fetchWorkItemDetails(workItemId, phaseKey);
|
||||||
}
|
}
|
||||||
if (selection.routingDetailId) {
|
if (selection.routingDetailId) {
|
||||||
await fetchWorkItems(selection.routingDetailId);
|
await fetchWorkItems(selection.routingDetailId);
|
||||||
|
|
@ -304,7 +324,7 @@ export function useProcessWorkStandard(config: ProcessWorkStandardConfig) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
selectedWorkItemId,
|
selectedWorkItemIdByPhase,
|
||||||
selection.routingDetailId,
|
selection.routingDetailId,
|
||||||
fetchWorkItemDetails,
|
fetchWorkItemDetails,
|
||||||
fetchWorkItems,
|
fetchWorkItems,
|
||||||
|
|
@ -315,8 +335,8 @@ export function useProcessWorkStandard(config: ProcessWorkStandardConfig) {
|
||||||
items,
|
items,
|
||||||
routings,
|
routings,
|
||||||
workItems,
|
workItems,
|
||||||
selectedWorkItemDetails,
|
selectedWorkItemIdByPhase,
|
||||||
selectedWorkItemId,
|
selectedDetailsByPhase,
|
||||||
selection,
|
selection,
|
||||||
loading,
|
loading,
|
||||||
saving,
|
saving,
|
||||||
|
|
@ -325,7 +345,6 @@ export function useProcessWorkStandard(config: ProcessWorkStandardConfig) {
|
||||||
selectProcess,
|
selectProcess,
|
||||||
fetchWorkItems,
|
fetchWorkItems,
|
||||||
fetchWorkItemDetails,
|
fetchWorkItemDetails,
|
||||||
setSelectedWorkItemId,
|
|
||||||
createWorkItem,
|
createWorkItem,
|
||||||
updateWorkItem,
|
updateWorkItem,
|
||||||
deleteWorkItem,
|
deleteWorkItem,
|
||||||
|
|
|
||||||
|
|
@ -3361,6 +3361,10 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// 🔧 그룹화된 데이터 렌더링
|
// 🔧 그룹화된 데이터 렌더링
|
||||||
|
const hasGroupedLeftActions = !isDesignMode && (
|
||||||
|
(componentConfig.leftPanel?.showEdit !== false) ||
|
||||||
|
(componentConfig.leftPanel?.showDelete !== false)
|
||||||
|
);
|
||||||
if (groupedLeftData.length > 0) {
|
if (groupedLeftData.length > 0) {
|
||||||
return (
|
return (
|
||||||
<div className="overflow-auto">
|
<div className="overflow-auto">
|
||||||
|
|
@ -3385,6 +3389,10 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
{col.label}
|
{col.label}
|
||||||
</th>
|
</th>
|
||||||
))}
|
))}
|
||||||
|
{hasGroupedLeftActions && (
|
||||||
|
<th className="px-3 py-2 text-right text-xs font-medium tracking-wider text-gray-500 uppercase whitespace-nowrap" style={{ width: "80px" }}>
|
||||||
|
</th>
|
||||||
|
)}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-gray-200 bg-white">
|
<tbody className="divide-y divide-gray-200 bg-white">
|
||||||
|
|
@ -3399,7 +3407,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
<tr
|
<tr
|
||||||
key={itemId}
|
key={itemId}
|
||||||
onClick={() => handleLeftItemSelect(item)}
|
onClick={() => handleLeftItemSelect(item)}
|
||||||
className={`hover:bg-accent cursor-pointer transition-colors ${
|
className={`group hover:bg-accent cursor-pointer transition-colors ${
|
||||||
isSelected ? "bg-primary/10" : ""
|
isSelected ? "bg-primary/10" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
|
@ -3417,6 +3425,34 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
))}
|
))}
|
||||||
|
{hasGroupedLeftActions && (
|
||||||
|
<td className="px-3 py-2 text-right">
|
||||||
|
<div className="flex items-center justify-end gap-1 opacity-0 transition-opacity group-hover:opacity-100">
|
||||||
|
{(componentConfig.leftPanel?.showEdit !== false) && (
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleEditClick("left", item);
|
||||||
|
}}
|
||||||
|
className="rounded p-1 transition-colors hover:bg-gray-200"
|
||||||
|
>
|
||||||
|
<Pencil className="h-3.5 w-3.5 text-gray-500" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{(componentConfig.leftPanel?.showDelete !== false) && (
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleDeleteClick("left", item);
|
||||||
|
}}
|
||||||
|
className="rounded p-1 transition-colors hover:bg-red-100"
|
||||||
|
>
|
||||||
|
<Trash2 className="h-3.5 w-3.5 text-red-500" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
)}
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
@ -3429,6 +3465,10 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔧 일반 테이블 렌더링 (그룹화 없음)
|
// 🔧 일반 테이블 렌더링 (그룹화 없음)
|
||||||
|
const hasLeftTableActions = !isDesignMode && (
|
||||||
|
(componentConfig.leftPanel?.showEdit !== false) ||
|
||||||
|
(componentConfig.leftPanel?.showDelete !== false)
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<div className="overflow-auto">
|
<div className="overflow-auto">
|
||||||
<table className="min-w-full divide-y divide-gray-200">
|
<table className="min-w-full divide-y divide-gray-200">
|
||||||
|
|
@ -3447,6 +3487,10 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
{col.label}
|
{col.label}
|
||||||
</th>
|
</th>
|
||||||
))}
|
))}
|
||||||
|
{hasLeftTableActions && (
|
||||||
|
<th className="px-3 py-2 text-right text-xs font-medium tracking-wider text-gray-500 uppercase whitespace-nowrap" style={{ width: "80px" }}>
|
||||||
|
</th>
|
||||||
|
)}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-gray-200 bg-white">
|
<tbody className="divide-y divide-gray-200 bg-white">
|
||||||
|
|
@ -3461,7 +3505,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
<tr
|
<tr
|
||||||
key={itemId}
|
key={itemId}
|
||||||
onClick={() => handleLeftItemSelect(item)}
|
onClick={() => handleLeftItemSelect(item)}
|
||||||
className={`hover:bg-accent cursor-pointer transition-colors ${
|
className={`group hover:bg-accent cursor-pointer transition-colors ${
|
||||||
isSelected ? "bg-primary/10" : ""
|
isSelected ? "bg-primary/10" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
|
@ -3479,6 +3523,34 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
))}
|
))}
|
||||||
|
{hasLeftTableActions && (
|
||||||
|
<td className="px-3 py-2 text-right">
|
||||||
|
<div className="flex items-center justify-end gap-1 opacity-0 transition-opacity group-hover:opacity-100">
|
||||||
|
{(componentConfig.leftPanel?.showEdit !== false) && (
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleEditClick("left", item);
|
||||||
|
}}
|
||||||
|
className="rounded p-1 transition-colors hover:bg-gray-200"
|
||||||
|
>
|
||||||
|
<Pencil className="h-3.5 w-3.5 text-gray-500" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{(componentConfig.leftPanel?.showDelete !== false) && (
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleDeleteClick("left", item);
|
||||||
|
}}
|
||||||
|
className="rounded p-1 transition-colors hover:bg-red-100"
|
||||||
|
>
|
||||||
|
<Trash2 className="h-3.5 w-3.5 text-red-500" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
)}
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue