import React, { useState, useEffect } from 'react';
import { FaTable, FaArrowRight, FaTimes, FaPlus, FaFilter, FaCalculator, FaExclamationTriangle } from 'react-icons/fa';
import { CustomDatabaseIcon, CustomDocumentIcon, CustomDimensionIcon, getTableIcon, CustomProcessIcon } from './CustomIcons';
const ProcessForm = ({ isOpen, onClose, onSave, tables, existingProcess = null }) => {
const [processName, setProcessName] = useState('');
const [processType, setProcessType] = useState('ETL');
const [description, setDescription] = useState('');
const [sourceTables, setSourceTables] = useState([]);
const [destinationTables, setDestinationTables] = useState([]);
const [mappings, setMappings] = useState([]);
const [filters, setFilters] = useState([]);
const [aggregations, setAggregations] = useState([]);
const [processStatus, setProcessStatus] = useState('inactive');
const [activeTab, setActiveTab] = useState('basic');
const [validationError, setValidationError] = useState('');
// Initialize form with existing process data if editing
useEffect(() => {
if (existingProcess) {
setProcessName(existingProcess.name || '');
setProcessType(existingProcess.type || 'ETL');
setDescription(existingProcess.description || '');
setSourceTables(existingProcess.source_table || []);
setDestinationTables(existingProcess.destination_table || []);
setProcessStatus(existingProcess.status || 'inactive');
// Initialize mappings, filters, and aggregations if they exist
if (existingProcess.mappings) setMappings(existingProcess.mappings);
if (existingProcess.filters) setFilters(existingProcess.filters);
if (existingProcess.aggregations) setAggregations(existingProcess.aggregations);
} else {
// Reset form for new process
setProcessName('');
setProcessType('ETL');
setDescription('');
setSourceTables([]);
setDestinationTables([]);
setMappings([]);
setFilters([]);
setAggregations([]);
setProcessStatus('inactive');
}
setValidationError('');
}, [existingProcess, isOpen]);
const handleSave = () => {
// Validate form
if (!processName.trim()) {
setValidationError('Please enter a process name');
return;
}
if (sourceTables.length === 0) {
setValidationError('Please select at least one source table');
return;
}
if (destinationTables.length === 0) {
setValidationError('Please select at least one destination table');
return;
}
// Create process object
const processData = {
name: processName,
type: processType,
description,
source_table: sourceTables,
destination_table: destinationTables,
mappings,
filters,
aggregations,
slug: existingProcess ? existingProcess.slug : `process_${Date.now()}`,
status: processStatus // Use the selected status
};
onSave(processData);
onClose();
};
const handleAddMapping = () => {
setMappings([...mappings, { source: '', target: '', type: 'direct' }]);
};
const handleUpdateMapping = (index, field, value) => {
const updatedMappings = [...mappings];
updatedMappings[index][field] = value;
setMappings(updatedMappings);
};
const handleRemoveMapping = (index) => {
setMappings(mappings.filter((_, i) => i !== index));
};
const handleAddFilter = () => {
setFilters([...filters, { column: '', operator: '=', value: '' }]);
};
const handleUpdateFilter = (index, field, value) => {
const updatedFilters = [...filters];
updatedFilters[index][field] = value;
setFilters(updatedFilters);
};
const handleRemoveFilter = (index) => {
setFilters(filters.filter((_, i) => i !== index));
};
const handleAddAggregation = () => {
setAggregations([...aggregations, { function: 'SUM', column: '', alias: '' }]);
};
const handleUpdateAggregation = (index, field, value) => {
const updatedAggregations = [...aggregations];
updatedAggregations[index][field] = value;
setAggregations(updatedAggregations);
};
const handleRemoveAggregation = (index) => {
setAggregations(aggregations.filter((_, i) => i !== index));
};
// Function to check if a table is a stage table
const isStageTable = (tableId) => {
const table = tables.find(t => t.slug === tableId);
return (table && table.type && table.type.toLowerCase() === 'stage') ||
(table && table.name && table.name.toLowerCase().includes('_stage'));
};
// Function to check if a table is a dimension table
const isDimensionTable = (tableId) => {
const table = tables.find(t => t.slug === tableId);
return (table && table.type && table.type.toLowerCase() === 'dimension') ||
(table && table.name && table.name.toLowerCase().includes('_dim') &&
!table.name.toLowerCase().includes('_stage') &&
!table.name.toLowerCase().includes('_fact'));
};
// Function to check if a table is a fact table
const isFactTable = (tableId) => {
const table = tables.find(t => t.slug === tableId);
return (table && table.type && table.type.toLowerCase() === 'fact') ||
(table && table.name && table.name.toLowerCase().includes('_fact'));
};
// Function to check if a connection is valid based on table types
const isValidConnection = (sourceId, destinationId) => {
// Stage tables can connect to either Dimension or Fact tables
if (isStageTable(sourceId)) {
return isDimensionTable(destinationId) || isFactTable(destinationId);
}
// Fact tables can connect to either Dimension or Fact tables
if (isFactTable(sourceId)) {
return isDimensionTable(destinationId) || isFactTable(destinationId);
}
// Dimension to Fact table connections are not allowed
if (isDimensionTable(sourceId) && isFactTable(destinationId)) {
return false;
}
// All other connections are allowed (including Dimension to Dimension)
return true;
};
// Check if a destination table can be selected based on current source tables
const canSelectDestination = (destinationId) => {
// If no source tables are selected, any destination can be selected
if (sourceTables.length === 0) {
return true;
}
// Check if any of the selected source tables can connect to this destination
return sourceTables.some(sourceId => isValidConnection(sourceId, destinationId));
};
// Check if a source table can be selected based on current destination tables
const canSelectSource = (sourceId) => {
// If no destination tables are selected, any source can be selected
if (destinationTables.length === 0) {
return true;
}
// Check if this source can connect to any of the selected destinations
return destinationTables.some(destinationId => isValidConnection(sourceId, destinationId));
};
const handleTableSelection = (tableId, type) => {
setValidationError('');
if (type === 'source') {
if (sourceTables.includes(tableId)) {
// Removing a source table
setSourceTables(sourceTables.filter(id => id !== tableId));
} else {
// Adding a source table - check if it's compatible with current destinations
if (canSelectSource(tableId)) {
setSourceTables([...sourceTables, tableId]);
} else {
setValidationError('This source table type cannot connect to the selected destination table(s). Dimension tables cannot connect to Fact tables.');
}
}
} else {
if (destinationTables.includes(tableId)) {
// Removing a destination table
setDestinationTables(destinationTables.filter(id => id !== tableId));
} else {
// Adding a destination table - check if it's compatible with current sources
if (canSelectDestination(tableId)) {
setDestinationTables([...destinationTables, tableId]);
} else {
setValidationError('This destination table type cannot be connected from the selected source table(s). Dimension tables cannot connect to Fact tables.');
}
}
}
};
// Get available columns for selected tables
const getAvailableColumns = (tableIds) => {
let columns = [];
tableIds.forEach(tableId => {
const table = tables.find(t => t.slug === tableId);
if (table && table.columns) {
// Ensure table type is properly set based on naming convention
if (table.name && table.name.toLowerCase().includes('_stage')) {
table.type = 'stage';
} else if (table.name && table.name.toLowerCase().includes('_fact')) {
table.type = 'fact';
} else if (table.name && table.name.toLowerCase().includes('_dim')) {
table.type = 'dimension';
} else if (table.type) {
// Normalize table type to lowercase for consistency
table.type = table.type.toLowerCase();
}
columns = [...columns, ...table.columns.map(col => ({
id: `${tableId}.${col}`,
name: `${table.name}.${col}`,
column: col,
table: tableId
}))];
}
});
return columns;
};
const sourceColumns = getAvailableColumns(sourceTables);
const destinationColumns = getAvailableColumns(destinationTables);
// Function to get table type label
const getTableTypeLabel = (tableType) => {
if (!tableType) return '';
switch (tableType.toLowerCase()) {
case 'dimension':
return 'DIM';
case 'fact':
return 'FACT';
case 'stage':
return 'STAGE';
default:
return tableType.toUpperCase();
}
};
// Function to get table type color
const getTableTypeColor = (tableType) => {
if (!tableType) return '#666666';
switch (tableType.toLowerCase()) {
case 'dimension':
return '#52c41a'; // Green
case 'fact':
return '#1890ff'; // Blue
case 'stage':
return '#fa8c16'; // Orange
default:
return '#666666';
}
};
// Function to get the appropriate icon based on table type
const getProcessTableIcon = (tableType) => {
if (!tableType) return
Stage tables (STAGE) serve as the master tables for loading metadata and data. They include error-handling functionality and support subset operations.