feat: Enhance image rendering in SplitPanelLayoutComponent
- Introduced SplitPanelCellImage component for rendering image thumbnails in table cells, supporting both object IDs and file paths. - Updated formatCellValue function to display images for columns with input type "image". - Improved loading logic for column input types to accommodate special rendering for images in both SplitPanelLayoutComponent and V2SplitPanelLayoutComponent. - Enhanced error handling for image loading failures, ensuring a better user experience when images cannot be displayed.
This commit is contained in:
@@ -25,7 +25,8 @@ import { dataApi } from "@/lib/api/data";
|
||||
import { entityJoinApi } from "@/lib/api/entityJoin";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { tableTypeApi } from "@/lib/api/screen";
|
||||
import { apiClient } from "@/lib/api/client";
|
||||
import { apiClient, getFullImageUrl } from "@/lib/api/client";
|
||||
import { getFilePreviewUrl } from "@/lib/api/file";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -51,6 +52,42 @@ export interface SplitPanelLayoutComponentProps extends ComponentRendererProps {
|
||||
selectedPanelComponentId?: string;
|
||||
}
|
||||
|
||||
// 이미지 셀 렌더링 컴포넌트 (objid 또는 파일 경로 지원)
|
||||
const SplitPanelCellImage: React.FC<{ value: string }> = React.memo(({ value }) => {
|
||||
const [imgSrc, setImgSrc] = React.useState<string | null>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!value) return;
|
||||
const strVal = String(value).trim();
|
||||
if (!strVal || strVal === "-") return;
|
||||
|
||||
if (strVal.startsWith("http") || strVal.startsWith("/uploads/") || strVal.startsWith("/api/")) {
|
||||
setImgSrc(getFullImageUrl(strVal));
|
||||
} else {
|
||||
const previewUrl = getFilePreviewUrl(strVal);
|
||||
fetch(previewUrl, { credentials: "include" })
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error("fetch failed");
|
||||
return res.blob();
|
||||
})
|
||||
.then((blob) => setImgSrc(URL.createObjectURL(blob)))
|
||||
.catch(() => setImgSrc(null));
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
if (!imgSrc) return <span className="text-muted-foreground text-xs">-</span>;
|
||||
|
||||
return (
|
||||
<img
|
||||
src={imgSrc}
|
||||
alt=""
|
||||
className="h-8 w-8 rounded object-cover"
|
||||
onError={() => setImgSrc(null)}
|
||||
/>
|
||||
);
|
||||
});
|
||||
SplitPanelCellImage.displayName = "SplitPanelCellImage";
|
||||
|
||||
/**
|
||||
* SplitPanelLayout 컴포넌트
|
||||
* 마스터-디테일 패턴의 좌우 분할 레이아웃
|
||||
@@ -210,6 +247,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
const [isLoadingLeft, setIsLoadingLeft] = useState(false);
|
||||
const [isLoadingRight, setIsLoadingRight] = useState(false);
|
||||
const [rightTableColumns, setRightTableColumns] = useState<any[]>([]); // 우측 테이블 컬럼 정보
|
||||
const [columnInputTypes, setColumnInputTypes] = useState<Record<string, string>>({});
|
||||
const [expandedItems, setExpandedItems] = useState<Set<any>>(new Set()); // 펼쳐진 항목들
|
||||
|
||||
// 추가 탭 관련 상태
|
||||
@@ -905,6 +943,12 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
) => {
|
||||
if (value === null || value === undefined) return "-";
|
||||
|
||||
// 이미지 타입 컬럼 처리
|
||||
const colInputType = columnInputTypes[columnName];
|
||||
if (colInputType === "image" && value) {
|
||||
return <SplitPanelCellImage value={String(value)} />;
|
||||
}
|
||||
|
||||
// 🆕 날짜 포맷 적용
|
||||
if (format?.type === "date" || format?.dateFormat) {
|
||||
return formatDateValue(value, format?.dateFormat || "YYYY-MM-DD");
|
||||
@@ -971,7 +1015,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
// 일반 값
|
||||
return String(value);
|
||||
},
|
||||
[formatDateValue, formatNumberValue],
|
||||
[formatDateValue, formatNumberValue, columnInputTypes],
|
||||
);
|
||||
|
||||
// 🆕 패널 config의 columns에서 additionalJoinColumns 추출하는 헬퍼
|
||||
@@ -1835,14 +1879,36 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
}
|
||||
});
|
||||
setRightColumnLabels(labels);
|
||||
console.log("✅ 우측 컬럼 라벨 로드:", labels);
|
||||
|
||||
// 컬럼 inputType 로드 (이미지 등 특수 렌더링을 위해)
|
||||
const tablesToLoad = new Set<string>([rightTableName]);
|
||||
const additionalTabs = componentConfig.rightPanel?.additionalTabs || [];
|
||||
additionalTabs.forEach((tab: any) => {
|
||||
if (tab.tableName) tablesToLoad.add(tab.tableName);
|
||||
});
|
||||
|
||||
const inputTypes: Record<string, string> = {};
|
||||
for (const tbl of tablesToLoad) {
|
||||
try {
|
||||
const inputTypesResponse = await tableTypeApi.getColumnInputTypes(tbl);
|
||||
inputTypesResponse.forEach((col: any) => {
|
||||
const colName = col.columnName || col.column_name;
|
||||
if (colName) {
|
||||
inputTypes[colName] = col.inputType || "text";
|
||||
}
|
||||
});
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
setColumnInputTypes(inputTypes);
|
||||
} catch (error) {
|
||||
console.error("우측 테이블 컬럼 정보 로드 실패:", error);
|
||||
}
|
||||
};
|
||||
|
||||
loadRightTableColumns();
|
||||
}, [componentConfig.rightPanel?.tableName, isDesignMode]);
|
||||
}, [componentConfig.rightPanel?.tableName, componentConfig.rightPanel?.additionalTabs, isDesignMode]);
|
||||
|
||||
// 좌측 테이블 카테고리 매핑 로드
|
||||
useEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user