diff --git a/frontend/components/dashboard/widgets/StatusSummaryWidget.tsx b/frontend/components/dashboard/widgets/StatusSummaryWidget.tsx index 5cec89f4..3fe1c427 100644 --- a/frontend/components/dashboard/widgets/StatusSummaryWidget.tsx +++ b/frontend/components/dashboard/widgets/StatusSummaryWidget.tsx @@ -53,9 +53,7 @@ const statusTranslations: { [key: string]: string } = { maintenance: "정비중", // 기사 관련 (존중하는 표현) - waiting: "대기중", resting: "휴식중", - unavailable: "운행불가", // 기사 평가 excellent: "우수", diff --git a/frontend/components/dataflow/SelectedTablesPanel.tsx b/frontend/components/dataflow/SelectedTablesPanel.tsx index 02e858b5..319051e4 100644 --- a/frontend/components/dataflow/SelectedTablesPanel.tsx +++ b/frontend/components/dataflow/SelectedTablesPanel.tsx @@ -64,7 +64,7 @@ export const SelectedTablesPanel: React.FC = ({
= ({
{displayName} @@ -81,7 +81,7 @@ export const SelectedTablesPanel: React.FC = ({ {selectedNodes.length === 2 && (
{index === 0 ? "FROM" : "TO"} diff --git a/frontend/components/dataflow/connection/redesigned/DataConnectionDesigner.tsx b/frontend/components/dataflow/connection/redesigned/DataConnectionDesigner.tsx index f13eb287..3fb71284 100644 --- a/frontend/components/dataflow/connection/redesigned/DataConnectionDesigner.tsx +++ b/frontend/components/dataflow/connection/redesigned/DataConnectionDesigner.tsx @@ -27,7 +27,6 @@ const initialState: DataConnectionState = { export const DataConnectionDesigner: React.FC = () => { const [state, setState] = useState(initialState); - const { isMobile, isTablet } = useResponsive(); return (
@@ -41,7 +40,7 @@ export const DataConnectionDesigner: React.FC = () => {
-
+
setState(prev => ({ ...prev, connectionType: type }))} diff --git a/frontend/components/pop/designer/PopCanvas.tsx b/frontend/components/pop/designer/PopCanvas.tsx index 41fa2955..ad99c219 100644 --- a/frontend/components/pop/designer/PopCanvas.tsx +++ b/frontend/components/pop/designer/PopCanvas.tsx @@ -1196,7 +1196,7 @@ function ModalSizeSettingsPanel({ > diff --git a/frontend/components/screen/InteractiveDataTable.tsx b/frontend/components/screen/InteractiveDataTable.tsx index 8cd4fc32..9d79cec5 100644 --- a/frontend/components/screen/InteractiveDataTable.tsx +++ b/frontend/components/screen/InteractiveDataTable.tsx @@ -308,6 +308,253 @@ export const InteractiveDataTable: React.FC = ({ [codeOptions], ); + // 데이터 로드 함수 (useEffect보다 먼저 선언해야 함 - TS2448 방지) + const loadData = useCallback( + async (page: number = 1, searchParams: Record = {}) => { + if (!component.tableName) return; + + // 프리뷰 모드에서는 샘플 데이터만 표시 + if (isPreviewMode) { + const sampleData = Array.from({ length: 3 }, (_, i) => { + const sample: Record = { id: i + 1 }; + component.columns.forEach((col) => { + if (col.widgetType === "number") { + sample[col.columnName] = Math.floor(Math.random() * 1000); + } else if (col.widgetType === "boolean") { + sample[col.columnName] = i % 2 === 0 ? "Y" : "N"; + } else { + sample[col.columnName] = `샘플 ${col.label} ${i + 1}`; + } + }); + return sample; + }); + setData(sampleData); + setTotal(3); + setTotalPages(1); + setCurrentPage(1); + setLoading(false); + return; + } + + setLoading(true); + try { + // 🆕 연결 필터 값 가져오기 (분할 패널 내부일 때) + let linkedFilterValues: Record = {}; + let hasLinkedFiltersConfigured = false; // 연결 필터가 설정되어 있는지 여부 + let hasSelectedLeftData = false; // 좌측에서 데이터가 선택되었는지 여부 + + if (splitPanelContext) { + // 연결 필터 설정 여부 확인 (현재 테이블에 해당하는 필터가 있는지) + const linkedFiltersConfig = splitPanelContext.linkedFilters || []; + hasLinkedFiltersConfigured = linkedFiltersConfig.some( + (filter) => + filter.targetColumn?.startsWith(component.tableName + ".") || filter.targetColumn === component.tableName, + ); + + // 좌측 데이터 선택 여부 확인 + hasSelectedLeftData = + splitPanelContext.selectedLeftData && Object.keys(splitPanelContext.selectedLeftData).length > 0; + + linkedFilterValues = splitPanelContext.getLinkedFilterValues(); + // 현재 테이블에 해당하는 필터만 추출 (테이블명.컬럼명 형식에서) + const tableSpecificFilters: Record = {}; + for (const [key, value] of Object.entries(linkedFilterValues)) { + // key가 "테이블명.컬럼명" 형식인 경우 + if (key.includes(".")) { + const [tableName, columnName] = key.split("."); + if (tableName === component.tableName) { + tableSpecificFilters[columnName] = value; + hasLinkedFiltersConfigured = true; // 이 테이블에 대한 필터가 있음 + } + } else { + // 테이블명 없이 컬럼명만 있는 경우 그대로 사용 + tableSpecificFilters[key] = value; + } + } + linkedFilterValues = tableSpecificFilters; + } + + // 🆕 연결 필터가 설정되어 있지만 좌측에서 데이터가 선택되지 않은 경우 + // → 빈 데이터 표시 (모든 데이터를 보여주지 않음) + if (hasLinkedFiltersConfigured && !hasSelectedLeftData) { + console.log("⚠️ [InteractiveDataTable] 연결 필터 설정됨 but 좌측 데이터 미선택 → 빈 데이터 표시"); + setData([]); + setTotal(0); + setTotalPages(0); + setCurrentPage(1); + setLoading(false); + return; + } + + // 🆕 RelatedDataButtons 필터 적용 + const relatedButtonFilterValues: Record = {}; + if (relatedButtonFilter) { + relatedButtonFilterValues[relatedButtonFilter.filterColumn] = relatedButtonFilter.filterValue; + } + + // 검색 파라미터와 연결 필터 병합 + const mergedSearchParams = { + ...searchParams, + ...linkedFilterValues, + ...relatedButtonFilterValues, // 🆕 RelatedDataButtons 필터 추가 + }; + + const currentPageSize = component.pagination?.pageSize || 10; + + console.log("🔍 데이터 조회 시작:", { + tableName: component.tableName, + page, + pageSize: currentPageSize, + linkedFilterValues, + relatedButtonFilterValues, + mergedSearchParams, + }); + + const result = await tableTypeApi.getTableData(component.tableName, { + page, + size: currentPageSize, + search: mergedSearchParams, + autoFilter: component.autoFilter, // 🆕 자동 필터 설정 전달 + }); + + console.log("✅ 데이터 조회 완료:", { + tableName: component.tableName, + dataLength: result.data.length, + total: result.total, + page: result.page, + }); + + setData(result.data); + setTotal(result.total); + setTotalPages(result.totalPages); + setCurrentPage(result.page); + + // 카테고리 코드 패턴(CATEGORY_*) 검출 및 라벨 조회 + const detectAndLoadCategoryLabels = async () => { + const categoryCodes = new Set(); + result.data.forEach((row: Record) => { + Object.values(row).forEach((value) => { + if (typeof value === "string" && value.startsWith("CATEGORY_")) { + categoryCodes.add(value); + } + }); + }); + + console.log("🏷️ [InteractiveDataTable] 감지된 카테고리 코드:", Array.from(categoryCodes)); + + // 새로운 카테고리 코드만 필터링 (이미 캐시된 것 제외) + const newCodes = Array.from(categoryCodes); + + if (newCodes.length > 0) { + try { + console.log("🏷️ [InteractiveDataTable] 카테고리 라벨 API 호출:", newCodes); + const response = await apiClient.post("/table-categories/labels-by-codes", { valueCodes: newCodes }); + console.log("🏷️ [InteractiveDataTable] 카테고리 라벨 API 응답:", response.data); + if (response.data.success && response.data.data) { + setCategoryCodeLabels((prev) => { + const newLabels = { + ...prev, + ...response.data.data, + }; + console.log("🏷️ [InteractiveDataTable] 카테고리 라벨 캐시 업데이트:", newLabels); + return newLabels; + }); + } + } catch (error) { + console.error("카테고리 라벨 조회 실패:", error); + } + } + }; + + detectAndLoadCategoryLabels(); + + // 각 행의 파일 상태 확인 (전체 행 + 가상 파일 컬럼별) + const fileStatusPromises = result.data.map(async (rowData: Record) => { + const primaryKeyField = Object.keys(rowData)[0]; + const recordId = rowData[primaryKeyField]; + + if (!recordId) return { rowKey: recordId, statuses: {} }; + + try { + const fileResponse = await getLinkedFiles(component.tableName, recordId); + const allFiles = fileResponse.files || []; + + // 전체 행에 대한 파일 상태 + const rowStatus = { + hasFiles: allFiles.length > 0, + fileCount: allFiles.length, + }; + + // 가상 파일 컬럼별 파일 상태 + const columnStatuses: Record = {}; + + // 가상 파일 컬럼 찾기 + const virtualFileColumns = component.columns.filter((col) => col.isVirtualFileColumn); + + virtualFileColumns.forEach((column) => { + // 해당 컬럼의 파일만 필터링 (targetObjid로 수정) + let columnFiles = allFiles.filter((file: any) => file.targetObjid?.endsWith(`:${column.columnName}`)); + + // fallback: 컬럼명으로 찾지 못한 경우 모든 파일 컬럼 파일 포함 + if (columnFiles.length === 0) { + columnFiles = allFiles.filter((file: any) => + file.targetObjid?.startsWith(`${component.tableName}:${recordId}:file_column_`), + ); + } + + const columnKey = `${recordId}_${column.columnName}`; + columnStatuses[columnKey] = { + hasFiles: columnFiles.length > 0, + fileCount: columnFiles.length, + }; + }); + + return { + rowKey: recordId, + statuses: { + [recordId]: rowStatus, // 전체 행 상태 + ...columnStatuses, // 컬럼별 상태 + }, + }; + } catch { + // 에러 시 기본값 + const defaultStatuses: Record = { + [recordId]: { hasFiles: false, fileCount: 0 }, + }; + + // 가상 파일 컬럼에 대해서도 기본값 설정 + const virtualFileColumns = component.columns.filter((col) => col.isVirtualFileColumn); + virtualFileColumns.forEach((column) => { + const columnKey = `${recordId}_${column.columnName}`; + defaultStatuses[columnKey] = { hasFiles: false, fileCount: 0 }; + }); + + return { rowKey: recordId, statuses: defaultStatuses }; + } + }); + + // 파일 상태 업데이트 + Promise.all(fileStatusPromises).then((results) => { + const statusMap: Record = {}; + + results.forEach((result) => { + Object.assign(statusMap, result.statuses); + }); + + setFileStatusMap(statusMap); + }); + } catch (error) { + // console.error("❌ 테이블 데이터 조회 실패:", error); + setData([]); + setTotal(0); + setTotalPages(1); + } finally { + setLoading(false); + } + }, + [component.tableName, component.pagination?.pageSize, component.autoFilter, splitPanelContext?.selectedLeftData, relatedButtonFilter], + ); + // 🆕 전역 테이블 새로고침 이벤트 리스너 useEffect(() => { const handleRefreshTable = () => { @@ -695,251 +942,6 @@ export const InteractiveDataTable: React.FC = ({ } }, [visibleColumns]); - // 데이터 로드 함수 - const loadData = useCallback( - async (page: number = 1, searchParams: Record = {}) => { - if (!component.tableName) return; - - // 프리뷰 모드에서는 샘플 데이터만 표시 - if (isPreviewMode) { - const sampleData = Array.from({ length: 3 }, (_, i) => { - const sample: Record = { id: i + 1 }; - component.columns.forEach((col) => { - if (col.widgetType === "number") { - sample[col.columnName] = Math.floor(Math.random() * 1000); - } else if (col.widgetType === "boolean") { - sample[col.columnName] = i % 2 === 0 ? "Y" : "N"; - } else { - sample[col.columnName] = `샘플 ${col.label} ${i + 1}`; - } - }); - return sample; - }); - setData(sampleData); - setTotal(3); - setTotalPages(1); - setCurrentPage(1); - setLoading(false); - return; - } - - setLoading(true); - try { - // 🆕 연결 필터 값 가져오기 (분할 패널 내부일 때) - let linkedFilterValues: Record = {}; - let hasLinkedFiltersConfigured = false; // 연결 필터가 설정되어 있는지 여부 - let hasSelectedLeftData = false; // 좌측에서 데이터가 선택되었는지 여부 - - if (splitPanelContext) { - // 연결 필터 설정 여부 확인 (현재 테이블에 해당하는 필터가 있는지) - const linkedFiltersConfig = splitPanelContext.linkedFilters || []; - hasLinkedFiltersConfigured = linkedFiltersConfig.some( - (filter) => - filter.targetColumn?.startsWith(component.tableName + ".") || filter.targetColumn === component.tableName, - ); - - // 좌측 데이터 선택 여부 확인 - hasSelectedLeftData = - splitPanelContext.selectedLeftData && Object.keys(splitPanelContext.selectedLeftData).length > 0; - - linkedFilterValues = splitPanelContext.getLinkedFilterValues(); - // 현재 테이블에 해당하는 필터만 추출 (테이블명.컬럼명 형식에서) - const tableSpecificFilters: Record = {}; - for (const [key, value] of Object.entries(linkedFilterValues)) { - // key가 "테이블명.컬럼명" 형식인 경우 - if (key.includes(".")) { - const [tableName, columnName] = key.split("."); - if (tableName === component.tableName) { - tableSpecificFilters[columnName] = value; - hasLinkedFiltersConfigured = true; // 이 테이블에 대한 필터가 있음 - } - } else { - // 테이블명 없이 컬럼명만 있는 경우 그대로 사용 - tableSpecificFilters[key] = value; - } - } - linkedFilterValues = tableSpecificFilters; - } - - // 🆕 연결 필터가 설정되어 있지만 좌측에서 데이터가 선택되지 않은 경우 - // → 빈 데이터 표시 (모든 데이터를 보여주지 않음) - if (hasLinkedFiltersConfigured && !hasSelectedLeftData) { - console.log("⚠️ [InteractiveDataTable] 연결 필터 설정됨 but 좌측 데이터 미선택 → 빈 데이터 표시"); - setData([]); - setTotal(0); - setTotalPages(0); - setCurrentPage(1); - setLoading(false); - return; - } - - // 🆕 RelatedDataButtons 필터 적용 - const relatedButtonFilterValues: Record = {}; - if (relatedButtonFilter) { - relatedButtonFilterValues[relatedButtonFilter.filterColumn] = relatedButtonFilter.filterValue; - } - - // 검색 파라미터와 연결 필터 병합 - const mergedSearchParams = { - ...searchParams, - ...linkedFilterValues, - ...relatedButtonFilterValues, // 🆕 RelatedDataButtons 필터 추가 - }; - - console.log("🔍 데이터 조회 시작:", { - tableName: component.tableName, - page, - pageSize, - linkedFilterValues, - relatedButtonFilterValues, - mergedSearchParams, - }); - - const result = await tableTypeApi.getTableData(component.tableName, { - page, - size: pageSize, - search: mergedSearchParams, - autoFilter: component.autoFilter, // 🆕 자동 필터 설정 전달 - }); - - console.log("✅ 데이터 조회 완료:", { - tableName: component.tableName, - dataLength: result.data.length, - total: result.total, - page: result.page, - }); - - setData(result.data); - setTotal(result.total); - setTotalPages(result.totalPages); - setCurrentPage(result.page); - - // 카테고리 코드 패턴(CATEGORY_*) 검출 및 라벨 조회 - const detectAndLoadCategoryLabels = async () => { - const categoryCodes = new Set(); - result.data.forEach((row: Record) => { - Object.values(row).forEach((value) => { - if (typeof value === "string" && value.startsWith("CATEGORY_")) { - categoryCodes.add(value); - } - }); - }); - - console.log("🏷️ [InteractiveDataTable] 감지된 카테고리 코드:", Array.from(categoryCodes)); - - // 새로운 카테고리 코드만 필터링 (이미 캐시된 것 제외) - const newCodes = Array.from(categoryCodes); - - if (newCodes.length > 0) { - try { - console.log("🏷️ [InteractiveDataTable] 카테고리 라벨 API 호출:", newCodes); - const response = await apiClient.post("/table-categories/labels-by-codes", { valueCodes: newCodes }); - console.log("🏷️ [InteractiveDataTable] 카테고리 라벨 API 응답:", response.data); - if (response.data.success && response.data.data) { - setCategoryCodeLabels((prev) => { - const newLabels = { - ...prev, - ...response.data.data, - }; - console.log("🏷️ [InteractiveDataTable] 카테고리 라벨 캐시 업데이트:", newLabels); - return newLabels; - }); - } - } catch (error) { - console.error("카테고리 라벨 조회 실패:", error); - } - } - }; - - detectAndLoadCategoryLabels(); - - // 각 행의 파일 상태 확인 (전체 행 + 가상 파일 컬럼별) - const fileStatusPromises = result.data.map(async (rowData: Record) => { - const primaryKeyField = Object.keys(rowData)[0]; - const recordId = rowData[primaryKeyField]; - - if (!recordId) return { rowKey: recordId, statuses: {} }; - - try { - const fileResponse = await getLinkedFiles(component.tableName, recordId); - const allFiles = fileResponse.files || []; - - // 전체 행에 대한 파일 상태 - const rowStatus = { - hasFiles: allFiles.length > 0, - fileCount: allFiles.length, - }; - - // 가상 파일 컬럼별 파일 상태 - const columnStatuses: Record = {}; - - // 가상 파일 컬럼 찾기 - const virtualFileColumns = component.columns.filter((col) => col.isVirtualFileColumn); - - virtualFileColumns.forEach((column) => { - // 해당 컬럼의 파일만 필터링 (targetObjid로 수정) - let columnFiles = allFiles.filter((file: any) => file.targetObjid?.endsWith(`:${column.columnName}`)); - - // fallback: 컬럼명으로 찾지 못한 경우 모든 파일 컬럼 파일 포함 - if (columnFiles.length === 0) { - columnFiles = allFiles.filter((file: any) => - file.targetObjid?.startsWith(`${component.tableName}:${recordId}:file_column_`), - ); - } - - const columnKey = `${recordId}_${column.columnName}`; - columnStatuses[columnKey] = { - hasFiles: columnFiles.length > 0, - fileCount: columnFiles.length, - }; - }); - - return { - rowKey: recordId, - statuses: { - [recordId]: rowStatus, // 전체 행 상태 - ...columnStatuses, // 컬럼별 상태 - }, - }; - } catch { - // 에러 시 기본값 - const defaultStatuses: Record = { - [recordId]: { hasFiles: false, fileCount: 0 }, - }; - - // 가상 파일 컬럼에 대해서도 기본값 설정 - const virtualFileColumns = component.columns.filter((col) => col.isVirtualFileColumn); - virtualFileColumns.forEach((column) => { - const columnKey = `${recordId}_${column.columnName}`; - defaultStatuses[columnKey] = { hasFiles: false, fileCount: 0 }; - }); - - return { rowKey: recordId, statuses: defaultStatuses }; - } - }); - - // 파일 상태 업데이트 - Promise.all(fileStatusPromises).then((results) => { - const statusMap: Record = {}; - - results.forEach((result) => { - Object.assign(statusMap, result.statuses); - }); - - setFileStatusMap(statusMap); - }); - } catch (error) { - // console.error("❌ 테이블 데이터 조회 실패:", error); - setData([]); - setTotal(0); - setTotalPages(1); - } finally { - setLoading(false); - } - }, - [component.tableName, pageSize, component.autoFilter, splitPanelContext?.selectedLeftData, relatedButtonFilter], // 🆕 autoFilter, 연결필터, RelatedDataButtons 필터 추가 - ); - // 현재 사용자 정보 로드 useEffect(() => { const fetchCurrentUser = async () => { @@ -2681,7 +2683,7 @@ export const InteractiveDataTable: React.FC = ({ className="h-32 text-center" >
- +

검색 결과가 없습니다

검색 조건을 변경하거나 새로고침을 시도해보세요

@@ -2759,7 +2761,7 @@ export const InteractiveDataTable: React.FC = ({ ) : (
- +

표시할 컬럼이 없습니다

테이블 설정에서 컬럼을 추가해주세요

diff --git a/frontend/components/screen/panels/ComponentsPanel.tsx b/frontend/components/screen/panels/ComponentsPanel.tsx index 4a2792e8..e0d914a9 100644 --- a/frontend/components/screen/panels/ComponentsPanel.tsx +++ b/frontend/components/screen/panels/ComponentsPanel.tsx @@ -231,17 +231,17 @@ export function ComponentsPanel({ const getCategoryColor = (category: string) => { switch (category) { case "data": - return "from-primary/50/10 to-primary/10 text-primary group-hover:from-primary/50/20 group-hover:to-primary/20"; + return "bg-primary/10 text-primary group-hover:bg-primary/20"; case "display": - return "from-emerald-500/10 to-emerald-600/10 text-emerald-600 group-hover:from-emerald-500/20 group-hover:to-emerald-600/20"; + return "bg-emerald-500/10 text-emerald-600 group-hover:bg-emerald-500/20"; case "input": - return "from-violet-500/10 to-violet-600/10 text-violet-600 group-hover:from-violet-500/20 group-hover:to-violet-600/20"; + return "bg-violet-500/10 text-violet-600 group-hover:bg-violet-500/20"; case "layout": - return "from-amber-500/10 to-amber-600/10 text-amber-600 group-hover:from-amber-500/20 group-hover:to-amber-600/20"; + return "bg-amber-500/10 text-amber-600 group-hover:bg-amber-500/20"; case "action": - return "from-rose-500/10 to-rose-600/10 text-rose-600 group-hover:from-rose-500/20 group-hover:to-rose-600/20"; + return "bg-rose-500/10 text-rose-600 group-hover:bg-rose-500/20"; default: - return "from-slate-500/10 to-slate-600/10 text-slate-600 group-hover:from-slate-500/20 group-hover:to-slate-600/20"; + return "bg-muted text-muted-foreground group-hover:bg-muted/80"; } }; @@ -263,7 +263,7 @@ export function ComponentsPanel({ >
{getCategoryIcon(component.category)}
diff --git a/frontend/components/screen/panels/DetailSettingsPanel.tsx b/frontend/components/screen/panels/DetailSettingsPanel.tsx index 8768c30f..81488444 100644 --- a/frontend/components/screen/panels/DetailSettingsPanel.tsx +++ b/frontend/components/screen/panels/DetailSettingsPanel.tsx @@ -813,7 +813,7 @@ export const DetailSettingsPanel: React.FC = ({ if (!selectedComponent) { return ( -
+
{/* 헤더 */}
@@ -1122,7 +1122,7 @@ export const DetailSettingsPanel: React.FC = ({ }; return ( -
+
{/* 헤더 */}
diff --git a/frontend/components/screen/panels/LayoutsPanel.tsx b/frontend/components/screen/panels/LayoutsPanel.tsx index 06cc902a..d4990eb4 100644 --- a/frontend/components/screen/panels/LayoutsPanel.tsx +++ b/frontend/components/screen/panels/LayoutsPanel.tsx @@ -147,7 +147,7 @@ export default function LayoutsPanel({ }; return ( -
+
{/* 헤더 */}
diff --git a/frontend/components/screen/panels/TemplatesPanel.tsx b/frontend/components/screen/panels/TemplatesPanel.tsx index e93ce6d5..5a34d8f9 100644 --- a/frontend/components/screen/panels/TemplatesPanel.tsx +++ b/frontend/components/screen/panels/TemplatesPanel.tsx @@ -487,7 +487,7 @@ export const TemplatesPanel: React.FC = ({ onDragStart }) = }); return ( -
+
{/* 헤더 */}

템플릿

@@ -570,7 +570,7 @@ export const TemplatesPanel: React.FC = ({ onDragStart }) = className="group cursor-grab rounded-lg border border-border/40 bg-white/90 backdrop-blur-sm p-6 shadow-sm transition-all duration-300 hover:bg-white hover:shadow-lg hover:shadow-blue-500/15 hover:scale-[1.02] hover:border-primary/40/60 hover:-translate-y-1 active:cursor-grabbing active:scale-[0.98] active:translate-y-0" >
-
+
{template.icon}
@@ -583,11 +583,11 @@ export const TemplatesPanel: React.FC = ({ onDragStart }) =

{template.description}

- + {template.defaultSize.width}×{template.defaultSize.height}
- + {template.category}
@@ -599,7 +599,7 @@ export const TemplatesPanel: React.FC = ({ onDragStart }) =
{/* 도움말 */} -
+
diff --git a/frontend/components/screen/widgets/FileUpload.tsx b/frontend/components/screen/widgets/FileUpload.tsx index fcb0a639..9248e944 100644 --- a/frontend/components/screen/widgets/FileUpload.tsx +++ b/frontend/components/screen/widgets/FileUpload.tsx @@ -141,7 +141,7 @@ export function FileUpload({ component, onUpdateComponent, onFileUpload, userInf }, [component.id]); const { fileConfig = {} } = component; - const { user: authUser, isLoading, isLoggedIn } = useAuth(); // 인증 상태도 함께 가져오기 + const { user: authUser, loading: isLoading, isLoggedIn } = useAuth(); // 인증 상태도 함께 가져오기 // props로 받은 userInfo를 우선 사용, 없으면 useAuth에서 가져온 user 사용 const user = userInfo || authUser; diff --git a/frontend/components/tax-invoice/TaxInvoiceDetail.tsx b/frontend/components/tax-invoice/TaxInvoiceDetail.tsx index e3632cc5..8d747b16 100644 --- a/frontend/components/tax-invoice/TaxInvoiceDetail.tsx +++ b/frontend/components/tax-invoice/TaxInvoiceDetail.tsx @@ -62,9 +62,9 @@ const statusLabels: Record = { // 상태 색상 const statusColors: Record = { draft: "bg-muted text-foreground", - issued: "bg-emerald-100 text-emerald-800", + issued: "bg-success/10 text-success", sent: "bg-primary/10 text-primary", - cancelled: "bg-destructive/10 text-red-800", + cancelled: "bg-destructive/10 text-destructive", }; export function TaxInvoiceDetail({ open, onClose, invoiceId }: TaxInvoiceDetailProps) { diff --git a/run-e2e-new-spec.sh b/run-e2e-new-spec.sh new file mode 100644 index 00000000..f1ac6437 --- /dev/null +++ b/run-e2e-new-spec.sh @@ -0,0 +1,39 @@ +#!/bin/bash +export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:$HOME/.nvm/versions/node/$(ls $HOME/.nvm/versions/node/ 2>/dev/null | tail -1)/bin" + +cd /Users/gbpark/ERP-node + +# Node 경로 찾기 +NODE_BIN="" +if command -v node &>/dev/null; then + NODE_BIN=$(command -v node) +elif [ -f "$HOME/.nvm/nvm.sh" ]; then + source "$HOME/.nvm/nvm.sh" + NODE_BIN=$(command -v node) +fi + +if [ -z "$NODE_BIN" ]; then + echo "BROWSER_TEST_RESULT: FAIL - node not found" + exit 1 +fi + +echo "Using node: $NODE_BIN" + +# playwright가 루트 node_modules에 있으므로 그걸로 실행 +PLAYWRIGHT_BIN="/Users/gbpark/ERP-node/node_modules/.bin/playwright" + +if [ -f "$PLAYWRIGHT_BIN" ]; then + echo "playwright binary found: $PLAYWRIGHT_BIN" + "$PLAYWRIGHT_BIN" test .agent-pipeline/browser-tests/e2e-test.spec.ts \ + --config=.agent-pipeline/browser-tests/playwright.config.ts \ + --reporter=line + EXIT=$? + if [ $EXIT -eq 0 ]; then + echo "BROWSER_TEST_RESULT: PASS" + else + echo "BROWSER_TEST_RESULT: FAIL - test failed with exit code $EXIT" + fi +else + echo "playwright binary not found, falling back to mjs runner" + $NODE_BIN /Users/gbpark/ERP-node/run-e2e-runtime-test.mjs +fi