first commit
This commit is contained in:
936
src/components/Dashboard/ReportRunner/ReportRunner2Edit.js
Normal file
936
src/components/Dashboard/ReportRunner/ReportRunner2Edit.js
Normal file
@@ -0,0 +1,936 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { fetchStandardParameters, downloadFile } from "../../../APIServices/ReportRunnerAPI";
|
||||
import ReportBuilderService from "../../../APIServices/ReportBuilderService";
|
||||
import { toast } from "react-toastify";
|
||||
import './ReportrunnerEdit.css'
|
||||
import { saveAs } from 'file-saver';
|
||||
|
||||
import axios from 'axios';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
const ReportRunner2Edit = () => {
|
||||
const navigate = useNavigate();
|
||||
const { id } = useParams();
|
||||
|
||||
// Report data state
|
||||
const [reportName, setReportName] = useState("Sample Report");
|
||||
const [reportId, setReportId] = useState(id || null);
|
||||
|
||||
// Date parameters state
|
||||
const [dateParam, setDateParam] = useState(true);
|
||||
const [selectDateType, setSelectDateType] = useState("");
|
||||
const [fromDate, setFromDate] = useState("");
|
||||
const [toDate, setToDate] = useState("");
|
||||
const [fromDateQuery, setFromDateQuery] = useState("");
|
||||
const [toDateQuery, setToDateQuery] = useState("");
|
||||
|
||||
// Standard parameters state
|
||||
const [dynamicFields, setDynamicFields] = useState([]);
|
||||
const [dynamicFormValues, setDynamicFormValues] = useState({});
|
||||
|
||||
// Adhoc parameters state
|
||||
const [adhocList, setAdhocList] = useState([]);
|
||||
const [adhocParams, setAdhocParams] = useState([
|
||||
{ andor: "AND", fields_name: "", condition: "=", value: "" },
|
||||
]);
|
||||
|
||||
// Condition options
|
||||
const conditionOptions = ["=", "!=", "<", ">", "<=", ">=", "LIKE", "BETWEEN", "IN"];
|
||||
const andOrOptions = ["AND", "OR", "NOT"];
|
||||
|
||||
// Results state
|
||||
const [rows, setRows] = useState([]);
|
||||
const [filterRows, setFilterRows] = useState([]);
|
||||
const [filtered, setFiltered] = useState(false);
|
||||
const [headers, setHeaders] = useState([]);
|
||||
|
||||
// Query state
|
||||
const [SQLQuery, setSQLQuery] = useState('');
|
||||
const [dateKey, setDateKey] = useState('createdat');
|
||||
const [formattedAdhocParameters, setFormattedAdhocParameters] = useState('');
|
||||
const [selectedValues, setSelectedValues] = useState({});
|
||||
|
||||
// Loading state
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
// Get today's date
|
||||
const todayDate = new Date().toISOString().slice(0, 10);
|
||||
|
||||
// Add this at the beginning of the component with other state variables
|
||||
const [showExportDropdown, setShowExportDropdown] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Fetch report details
|
||||
if (reportId) {
|
||||
fetchReportDetails(reportId);
|
||||
}
|
||||
|
||||
// Set default date to today
|
||||
handleDateSelect('Today');
|
||||
|
||||
// Run query after a delay to allow data to load
|
||||
const timer = setTimeout(() => {
|
||||
runQuery();
|
||||
}, 2000);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [reportId]);
|
||||
|
||||
useEffect(() => {
|
||||
// Function to handle clicks outside the dropdown
|
||||
const handleClickOutside = (event) => {
|
||||
// Get reference to the dropdown container
|
||||
const dropdownContainer = document.getElementById('export-dropdown-container');
|
||||
|
||||
// Close dropdown if click is outside the container
|
||||
if (dropdownContainer && !dropdownContainer.contains(event.target)) {
|
||||
setShowExportDropdown(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Function to handle escape key press
|
||||
const handleEscapeKey = (event) => {
|
||||
if (event.key === 'Escape') {
|
||||
setShowExportDropdown(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Add event listeners if dropdown is open
|
||||
if (showExportDropdown) {
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
document.addEventListener('keydown', handleEscapeKey);
|
||||
}
|
||||
|
||||
// Clean up event listeners when dropdown closes or component unmounts
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
document.removeEventListener('keydown', handleEscapeKey);
|
||||
};
|
||||
}, [showExportDropdown]);
|
||||
|
||||
const fetchReportDetails = async (id) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
// Fetch report details
|
||||
const response = await ReportBuilderService.getrbDetailsById(id);
|
||||
const data = response?.data;
|
||||
if (!data) {
|
||||
throw new Error("No data received from the API");
|
||||
}
|
||||
console.log("Report details received:", data);
|
||||
setReportName(data.reportName);
|
||||
// Parse builder line data
|
||||
const builderLine = data.rpt_builder2_lines?.[0];
|
||||
if (builderLine) {
|
||||
const lineData = JSON.parse(builderLine.model)?.[0];
|
||||
console.log("lineData: ",lineData);
|
||||
if (lineData) {
|
||||
setAdhocList(lineData.adhoc_param_html || []);
|
||||
setDateParam(lineData.date_param_req || false);
|
||||
setSQLQuery(lineData.url || "");
|
||||
// Set dynamic fields from standard parameters
|
||||
const dynamicFields = lineData.std_param_html || [];
|
||||
setDynamicFields(dynamicFields);
|
||||
// Initialize dynamic form values
|
||||
const initialFormValues = dynamicFields.reduce((acc, field) => {
|
||||
acc[field] = "";
|
||||
return acc;
|
||||
}, {});
|
||||
setDynamicFormValues(initialFormValues);
|
||||
// Fetch data if a URL exists
|
||||
if (lineData.url) {
|
||||
fetchData(lineData.url);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching report details:", error);
|
||||
setError("Error loading report details");
|
||||
toast.error("Failed to load report details");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const fetchData = async (url) => {
|
||||
try {
|
||||
const response = await ReportBuilderService.getAllDetailsByurl(url);
|
||||
console.log("responce of data fetch from url:", response.data)
|
||||
const data = response.data?.body ? JSON.parse(response.data.body) : [];
|
||||
setRows(data);
|
||||
setFilterRows(data);
|
||||
|
||||
// Set headers from the first row if available
|
||||
if (data.length > 0) {
|
||||
setHeaders(Object.keys(data[0]));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error);
|
||||
setError("Error loading data");
|
||||
}
|
||||
};
|
||||
|
||||
// Handle changes to adhoc parameters
|
||||
const handleAdhocChange = (index, field, value) => {
|
||||
const updatedParams = [...adhocParams];
|
||||
updatedParams[index][field] = value;
|
||||
setAdhocParams(updatedParams);
|
||||
};
|
||||
|
||||
// Add a new adhoc parameter row
|
||||
const addAdhocRow = () => {
|
||||
const lastRow = adhocParams[adhocParams.length - 1];
|
||||
|
||||
// Only add a new row if the last row has a field name
|
||||
if (lastRow && lastRow.fields_name !== '') {
|
||||
// Format the adhoc parameters for the query
|
||||
let formattedString = '';
|
||||
for (const condition of adhocParams) {
|
||||
const { andor, fields_name, condition: cond, value } = condition;
|
||||
formattedString += ` ${andor} ${fields_name} ${cond} '${value}' `;
|
||||
}
|
||||
setFormattedAdhocParameters(formattedString);
|
||||
|
||||
// Add a new row
|
||||
setAdhocParams([
|
||||
...adhocParams,
|
||||
{ andor: "AND", fields_name: "", condition: "=", value: "" },
|
||||
]);
|
||||
|
||||
// Update selected values for filtering
|
||||
selectColumn(adhocParams);
|
||||
}
|
||||
};
|
||||
|
||||
// Delete an adhoc parameter row
|
||||
const deleteAdhocRow = (index) => {
|
||||
if (adhocParams.length > 1) {
|
||||
// Get the item to be deleted
|
||||
const deletedItem = adhocParams[index];
|
||||
|
||||
// Remove the item from the adhocParams array
|
||||
const updatedParams = adhocParams.filter((_, i) => i !== index);
|
||||
setAdhocParams(updatedParams);
|
||||
|
||||
// Update selected values
|
||||
const updatedSelectedValues = { ...selectedValues };
|
||||
const columnName = deletedItem.fields_name;
|
||||
|
||||
if (updatedSelectedValues[columnName]) {
|
||||
const value = deletedItem.value;
|
||||
const indexInArray = updatedSelectedValues[columnName].indexOf(value);
|
||||
if (indexInArray !== -1) {
|
||||
updatedSelectedValues[columnName].splice(indexInArray, 1);
|
||||
|
||||
// If the array is now empty, remove the property
|
||||
if (updatedSelectedValues[columnName].length === 0) {
|
||||
delete updatedSelectedValues[columnName];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setSelectedValues(updatedSelectedValues);
|
||||
filterRowsBySelectedValues(updatedSelectedValues);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle dynamic form values change
|
||||
const handleDynamicFormChange = (field, value) => {
|
||||
setDynamicFormValues({
|
||||
...dynamicFormValues,
|
||||
[field]: value
|
||||
});
|
||||
};
|
||||
|
||||
// Handle date type selection
|
||||
const handleDateSelect = (dateType) => {
|
||||
setSelectDateType(dateType);
|
||||
setFromDateQuery(null);
|
||||
setToDateQuery(null);
|
||||
|
||||
const currentDate = new Date();
|
||||
let fromDateValue, toDateValue;
|
||||
|
||||
switch (dateType) {
|
||||
case 'Today':
|
||||
fromDateValue = new Date();
|
||||
toDateValue = new Date();
|
||||
break;
|
||||
|
||||
case 'This Week':
|
||||
// Calculate this week (Monday to Sunday)
|
||||
const dayOfWeek = currentDate.getDay();
|
||||
const daysToMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
|
||||
|
||||
fromDateValue = new Date(currentDate);
|
||||
fromDateValue.setDate(currentDate.getDate() - daysToMonday);
|
||||
|
||||
toDateValue = new Date(fromDateValue);
|
||||
toDateValue.setDate(fromDateValue.getDate() + 6);
|
||||
break;
|
||||
|
||||
case 'Last Week':
|
||||
// Calculate last week
|
||||
const lastWeekDayOfWeek = currentDate.getDay();
|
||||
const lastWeekDaysToMonday = lastWeekDayOfWeek === 0 ? 6 : lastWeekDayOfWeek - 1;
|
||||
|
||||
fromDateValue = new Date(currentDate);
|
||||
fromDateValue.setDate(currentDate.getDate() - lastWeekDaysToMonday - 7);
|
||||
|
||||
toDateValue = new Date(fromDateValue);
|
||||
toDateValue.setDate(fromDateValue.getDate() + 6);
|
||||
break;
|
||||
|
||||
case 'This Month':
|
||||
// First day of current month
|
||||
fromDateValue = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1);
|
||||
// Last day of current month
|
||||
toDateValue = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0);
|
||||
break;
|
||||
|
||||
case 'Last Month':
|
||||
// First day of previous month
|
||||
fromDateValue = new Date(currentDate.getFullYear(), currentDate.getMonth() - 1, 1);
|
||||
// Last day of previous month
|
||||
toDateValue = new Date(currentDate.getFullYear(), currentDate.getMonth(), 0);
|
||||
break;
|
||||
|
||||
case 'This Year':
|
||||
// First day of current year
|
||||
fromDateValue = new Date(currentDate.getFullYear(), 0, 1);
|
||||
// Last day of current year
|
||||
toDateValue = new Date(currentDate.getFullYear(), 11, 31);
|
||||
break;
|
||||
|
||||
case 'Last Year':
|
||||
// First day of previous year
|
||||
fromDateValue = new Date(currentDate.getFullYear() - 1, 0, 1);
|
||||
// Last day of previous year
|
||||
toDateValue = new Date(currentDate.getFullYear() - 1, 11, 31);
|
||||
break;
|
||||
|
||||
default:
|
||||
fromDateValue = null;
|
||||
toDateValue = null;
|
||||
}
|
||||
|
||||
if (fromDateValue) {
|
||||
const fromDateString = fromDateValue.toISOString().substring(0, 10);
|
||||
setFromDate(fromDateString);
|
||||
setFromDateQuery(fromDateString);
|
||||
} else {
|
||||
setFromDate('');
|
||||
setFromDateQuery('');
|
||||
}
|
||||
|
||||
if (toDateValue) {
|
||||
const toDateString = toDateValue.toISOString().substring(0, 10);
|
||||
setToDate(toDateString);
|
||||
setToDateQuery(toDateString);
|
||||
} else {
|
||||
setToDate('');
|
||||
setToDateQuery('');
|
||||
}
|
||||
};
|
||||
|
||||
// Run the query
|
||||
const runQuery = () => {
|
||||
console.log("Dynamic form values:", dynamicFormValues);
|
||||
console.log("Date range:", fromDate, toDate);
|
||||
|
||||
let query = SQLQuery || '';
|
||||
|
||||
// Add standard parameters to the query
|
||||
if (Object.keys(dynamicFormValues).length > 0) {
|
||||
Object.keys(dynamicFormValues).forEach((key) => {
|
||||
if (dynamicFormValues[key] !== null && dynamicFormValues[key] !== '') {
|
||||
query += ` AND ${key} = '${dynamicFormValues[key]}'`;
|
||||
}
|
||||
});
|
||||
|
||||
// Update selected values for filtering
|
||||
selectColumn(dynamicFormValues);
|
||||
}
|
||||
|
||||
// Add date range to the query if date parameter is required
|
||||
if (dateParam) {
|
||||
// Determine the correct date key based on adhoc list
|
||||
let tempDateKey = 'createdat';
|
||||
adhocList.forEach(key => {
|
||||
if (key.includes('created_at')) tempDateKey = 'created_at';
|
||||
if (key.includes('createdAt')) tempDateKey = 'createdAt';
|
||||
});
|
||||
setDateKey(tempDateKey);
|
||||
|
||||
if (fromDate && toDate) {
|
||||
const fromDateObj = new Date(fromDate);
|
||||
const toDateObj = new Date(toDate);
|
||||
|
||||
query += ` AND ${tempDateKey} BETWEEN '${fromDateObj.toISOString().split('T')[0]}' AND '${toDateObj.toISOString().split('T')[0]}'`;
|
||||
}
|
||||
}
|
||||
|
||||
// Format and add adhoc parameters to the query
|
||||
let formattedString = '';
|
||||
for (const condition of adhocParams) {
|
||||
// Only add conditions that have a field name selected
|
||||
if (condition.fields_name) {
|
||||
const { andor, fields_name, condition: cond, value } = condition;
|
||||
formattedString += ` ${andor} ${fields_name} ${cond} '${value}' `;
|
||||
}
|
||||
}
|
||||
setFormattedAdhocParameters(formattedString);
|
||||
|
||||
// Add adhoc parameters to the query
|
||||
if (formattedString) {
|
||||
query += formattedString;
|
||||
}
|
||||
|
||||
// Update selected values for filtering based on adhoc parameters
|
||||
selectColumn(adhocParams.filter(param => param.fields_name));
|
||||
|
||||
console.log("Final query:", query);
|
||||
|
||||
// Make API request
|
||||
// axios.post('/api/report-runner/run', { query })
|
||||
// .then((response) => {
|
||||
// const data = response.data;
|
||||
|
||||
// if (data && data.length > 0) {
|
||||
// setRows(data);
|
||||
// setFilterRows(data);
|
||||
// setHeaders(Object.keys(data[0]));
|
||||
// toast.success("Query executed successfully");
|
||||
// } else {
|
||||
// toast.warning("No data returned");
|
||||
// }
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// console.error("Error running query:", error);
|
||||
// toast.error("Error running the query");
|
||||
// });
|
||||
};
|
||||
|
||||
// Export file function
|
||||
const exportFile = (format) => {
|
||||
try {
|
||||
const dataToExport = filtered ? filterRows : rows;
|
||||
|
||||
if (!dataToExport || dataToExport.length === 0) {
|
||||
toast.warning("No data to export");
|
||||
return;
|
||||
}
|
||||
|
||||
const name = reportName.replace(/\s+/g, '_');
|
||||
const timestamp = new Date().toISOString().replace(/[-:T.]/g, '_').slice(0, 17);
|
||||
const fileName = `${name}_${timestamp}`;
|
||||
|
||||
// Show loading toast
|
||||
const loadingToast = toast.info("Preparing file for export...", { autoClose: false });
|
||||
|
||||
// Use the imported downloadFile service
|
||||
downloadFile(format, dataToExport, fileName);
|
||||
|
||||
// Success message
|
||||
setTimeout(() => {
|
||||
toast.dismiss(loadingToast);
|
||||
toast.success(`File exported as ${format.toUpperCase()} successfully`);
|
||||
}, 1000);
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error in export function:", error);
|
||||
toast.error("Error exporting file");
|
||||
}
|
||||
};
|
||||
|
||||
// Get table headers
|
||||
const getHeaders = () => {
|
||||
if (!rows || rows.length === 0) return [];
|
||||
|
||||
const headerSet = new Set();
|
||||
rows.forEach(row => {
|
||||
Object.keys(row).forEach(key => headerSet.add(key));
|
||||
});
|
||||
|
||||
return Array.from(headerSet);
|
||||
};
|
||||
|
||||
// Get filtered table headers
|
||||
const getFilteredHeaders = () => {
|
||||
if (!filterRows || filterRows.length === 0) return [];
|
||||
|
||||
const headerSet = new Set();
|
||||
filterRows.forEach(row => {
|
||||
Object.keys(row).forEach(key => headerSet.add(key));
|
||||
});
|
||||
|
||||
return Array.from(headerSet);
|
||||
};
|
||||
|
||||
// Check if a value is a date
|
||||
const isDate = (value) => {
|
||||
if (!value) return false;
|
||||
|
||||
if (value instanceof Date) return true;
|
||||
|
||||
if (typeof value === 'object' &&
|
||||
value.year !== undefined &&
|
||||
value.monthValue !== undefined &&
|
||||
value.dayOfMonth !== undefined) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const date = new Date(value);
|
||||
return !isNaN(date.getTime());
|
||||
};
|
||||
|
||||
// Format a date for display
|
||||
const formatDate = (value) => {
|
||||
if (!value) return '';
|
||||
|
||||
if (typeof value === 'object' &&
|
||||
value.year !== undefined &&
|
||||
value.monthValue !== undefined &&
|
||||
value.dayOfMonth !== undefined) {
|
||||
// Handle Java-style date objects
|
||||
const { year, monthValue, dayOfMonth, hour = 0, minute = 0, second = 0 } = value;
|
||||
const date = new Date(year, monthValue - 1, dayOfMonth, hour, minute, second);
|
||||
return date.toLocaleString();
|
||||
}
|
||||
|
||||
// Standard JS date
|
||||
return new Date(value).toLocaleString();
|
||||
};
|
||||
|
||||
// Process data for filtering
|
||||
const selectColumn = (data) => {
|
||||
const newSelectedValues = { ...selectedValues };
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
// Handle array of objects (like adhoc parameters)
|
||||
data.forEach(item => {
|
||||
const { fields_name, value } = item;
|
||||
|
||||
if (fields_name && fields_name.trim() !== '') {
|
||||
if (!newSelectedValues[fields_name]) {
|
||||
newSelectedValues[fields_name] = [];
|
||||
}
|
||||
|
||||
if (value !== null && value.trim() !== '') {
|
||||
if (!newSelectedValues[fields_name].includes(value)) {
|
||||
newSelectedValues[fields_name].push(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (typeof data === 'object') {
|
||||
// Handle object (like dynamic form values)
|
||||
Object.keys(data).forEach(key => {
|
||||
const value = data[key];
|
||||
|
||||
if (!newSelectedValues[key]) {
|
||||
newSelectedValues[key] = [];
|
||||
}
|
||||
|
||||
if (value !== null && value.trim && value.trim() !== '') {
|
||||
if (!newSelectedValues[key].includes(value)) {
|
||||
newSelectedValues[key].push(value);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setSelectedValues(newSelectedValues);
|
||||
filterRowsBySelectedValues(newSelectedValues);
|
||||
};
|
||||
|
||||
// Filter rows based on selected values
|
||||
const filterRowsBySelectedValues = (values = selectedValues) => {
|
||||
const filteredRows = [];
|
||||
|
||||
for (const row of rows) {
|
||||
let isMatch = true;
|
||||
|
||||
// Check each column in the selected values
|
||||
for (const columnName in values) {
|
||||
if (values.hasOwnProperty(columnName) && row.hasOwnProperty(columnName)) {
|
||||
const selectedValuesForColumn = values[columnName];
|
||||
const rowValue = row[columnName];
|
||||
|
||||
if (typeof rowValue === 'boolean') {
|
||||
// Handle boolean values
|
||||
if (selectedValuesForColumn.length === 0) continue;
|
||||
|
||||
const selectedBooleanValue = selectedValuesForColumn[0] === 'true';
|
||||
if (selectedBooleanValue !== rowValue) {
|
||||
isMatch = false;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Handle other data types
|
||||
const convertedValues = selectedValuesForColumn.map(value => {
|
||||
if (typeof rowValue === 'number') {
|
||||
return parseFloat(value);
|
||||
}
|
||||
return value;
|
||||
});
|
||||
|
||||
if (!convertedValues.includes(rowValue)) {
|
||||
isMatch = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check date range if both dates are provided
|
||||
if (fromDateQuery && toDateQuery && isMatch) {
|
||||
const from = new Date(fromDateQuery);
|
||||
const to = new Date(toDateQuery);
|
||||
|
||||
// Set hours to 0 for proper comparison
|
||||
from.setHours(0, 0, 0, 0);
|
||||
to.setHours(23, 59, 59, 999);
|
||||
|
||||
// Get the date from the row using dateKey
|
||||
const rowDate = new Date(row[dateKey]);
|
||||
|
||||
if (rowDate < from || rowDate > to) {
|
||||
isMatch = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isMatch) {
|
||||
filteredRows.push(row);
|
||||
}
|
||||
}
|
||||
|
||||
setFilterRows(filteredRows);
|
||||
|
||||
// Determine if we are filtering or not
|
||||
const isFiltering = Object.values(values).some(arr => arr.length > 0) ||
|
||||
(fromDateQuery && toDateQuery);
|
||||
|
||||
setFiltered(isFiltering);
|
||||
};
|
||||
|
||||
// Render date cell with formatting
|
||||
const renderTableCell = (row, key) => {
|
||||
const isDateField = key === 'createdat' || key === 'createdAt' ||
|
||||
key === 'updated_at' || key === 'updatedAt' ||
|
||||
key === 'created_at' || key === 'creat_at';
|
||||
|
||||
if (isDateField && row[key]) {
|
||||
return formatDate(row[key]);
|
||||
}
|
||||
|
||||
// Handle boolean values
|
||||
if (typeof row[key] === 'boolean') {
|
||||
return row[key] ? 'True' : 'False';
|
||||
}
|
||||
|
||||
return row[key];
|
||||
};
|
||||
|
||||
// Navigate back to the reports list
|
||||
const goBack = () => {
|
||||
navigate("/admin/report-runner");
|
||||
};
|
||||
|
||||
// Add this function at an appropriate place in the component
|
||||
const toggleExportDropdown = () => {
|
||||
setShowExportDropdown(!showExportDropdown);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
{/* Report Header */}
|
||||
<h4 style={{ fontWeight: 300, display: "inline" }}>
|
||||
<b>Report Name URL - {reportName}</b>
|
||||
</h4>
|
||||
<hr />
|
||||
|
||||
{/* Date and Standard Parameters Section */}
|
||||
<div className="row">
|
||||
{/* Date Parameters */}
|
||||
{/* {dateParam && (
|
||||
<div className="col-md-6">
|
||||
<h5 style={{ fontWeight: 200, color: "black" }}>
|
||||
<b>Date Range</b>
|
||||
</h5>
|
||||
<div className="form-group mb-3">
|
||||
<label>Date Parameters</label>
|
||||
<select
|
||||
className="form-select"
|
||||
value={selectDateType}
|
||||
onChange={(e) => handleDateSelect(e.target.value)}
|
||||
>
|
||||
<option>--Select Particular--</option>
|
||||
<option>Today</option>
|
||||
<option>This Week</option>
|
||||
<option>Last Week</option>
|
||||
<option>This Month</option>
|
||||
<option>Last Month</option>
|
||||
<option>This Year</option>
|
||||
<option>Last Year</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-md-6">
|
||||
<label>From Date</label>
|
||||
<input
|
||||
type="date"
|
||||
className="form-control"
|
||||
value={fromDate}
|
||||
onChange={(e) => {
|
||||
setFromDate(e.target.value);
|
||||
setFromDateQuery(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<label>To Date</label>
|
||||
<input
|
||||
type="date"
|
||||
className="form-control"
|
||||
value={toDate}
|
||||
onChange={(e) => {
|
||||
setToDate(e.target.value);
|
||||
setToDateQuery(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)} */}
|
||||
|
||||
{/* Standard Parameters */}
|
||||
<div className="col-md-6">
|
||||
<h5 style={{ fontWeight: 200, color: "black" }}>
|
||||
<b>Standard Parameters</b>
|
||||
</h5>
|
||||
{dynamicFields.length === 0 ? (
|
||||
<div className="text-danger">No parameter found</div>
|
||||
) : (
|
||||
<div className="row">
|
||||
{dynamicFields.map((field, index) => (
|
||||
<div key={index} className="col-md-6 mb-3">
|
||||
<label>{field}</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={`Enter ${field}`}
|
||||
value={dynamicFormValues[field] || ''}
|
||||
onChange={(e) => handleDynamicFormChange(field, e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Adhoc Parameters Section */}
|
||||
<div className="row mt-4">
|
||||
<div className="col-md-12">
|
||||
<h5 style={{ fontWeight: 200, color: "black" }}>
|
||||
<b>Adhoc Parameters</b>
|
||||
</h5>
|
||||
<table className="table">
|
||||
<tbody>
|
||||
{adhocParams.map((param, index) => (
|
||||
<tr key={index}>
|
||||
<td>
|
||||
<select
|
||||
className="form-select"
|
||||
value={param.andor}
|
||||
onChange={(e) =>
|
||||
handleAdhocChange(index, "andor", e.target.value)
|
||||
}
|
||||
>
|
||||
<option value="">Select Values</option>
|
||||
{andOrOptions.map((option, idx) => (
|
||||
<option key={idx} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<select
|
||||
className="form-select"
|
||||
value={param.fields_name}
|
||||
onChange={(e) =>
|
||||
handleAdhocChange(index, "fields_name", e.target.value)
|
||||
}
|
||||
>
|
||||
<option value="">Select Values</option>
|
||||
{adhocList.map((field, idx) => (
|
||||
<option key={idx} value={field}>
|
||||
{field}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<select
|
||||
className="form-select"
|
||||
value={param.condition}
|
||||
onChange={(e) =>
|
||||
handleAdhocChange(index, "condition", e.target.value)
|
||||
}
|
||||
>
|
||||
<option value="">Select Values</option>
|
||||
{conditionOptions.map((condition, idx) => (
|
||||
<option key={idx} value={condition}>
|
||||
{condition}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
value={param.value}
|
||||
onChange={(e) =>
|
||||
handleAdhocChange(index, "value", e.target.value)
|
||||
}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
className="btn btn-danger me-2"
|
||||
onClick={() => deleteAdhocRow(index)}
|
||||
title="Delete Row"
|
||||
>
|
||||
<i className="bi bi-trash"></i>
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
onClick={addAdhocRow}
|
||||
title="Add Row"
|
||||
>
|
||||
<i className="bi bi-plus"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Report Output Section */}
|
||||
<div className="row mt-4">
|
||||
<div className="col-md-6">
|
||||
<h5 style={{ fontWeight: 300 }}>
|
||||
<b>Report Output</b>
|
||||
</h5>
|
||||
</div>
|
||||
<div className="col-md-6 text-end">
|
||||
{/* Custom Export Dropdown */}
|
||||
<div
|
||||
id="export-dropdown-container"
|
||||
className="d-inline-block me-2"
|
||||
style={{ position: 'relative' }}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-outline-primary mr-3"
|
||||
onClick={toggleExportDropdown}
|
||||
>
|
||||
<i className="bi bi-download me-1"></i> Export <i className="bi bi-caret-down-fill ms-1 small "></i>
|
||||
</button>
|
||||
{showExportDropdown && (
|
||||
<div
|
||||
className="shadow position-absolute end-0 bg-white border rounded mt-1 py-1"
|
||||
style={{ zIndex: 1000, minWidth: '160px' }}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className="dropdown-item d-flex align-items-center px-3 py-2"
|
||||
onClick={() => {
|
||||
exportFile("xlsx");
|
||||
setShowExportDropdown(false);
|
||||
}}
|
||||
>
|
||||
<i className="bi bi-file-earmark-excel me-2 text-success"></i> XLSX
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="dropdown-item d-flex align-items-center px-3 py-2"
|
||||
onClick={() => {
|
||||
exportFile("csv");
|
||||
setShowExportDropdown(false);
|
||||
}}
|
||||
>
|
||||
<i className="bi bi-file-earmark-text me-2 text-primary"></i> CSV
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="dropdown-item d-flex align-items-center px-3 py-2"
|
||||
onClick={() => {
|
||||
exportFile("pdf");
|
||||
setShowExportDropdown(false);
|
||||
}}
|
||||
>
|
||||
<i className="bi bi-file-earmark-pdf me-2 text-danger"></i> PDF
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Back and Run Buttons */}
|
||||
<button className="btn btn-outline-secondary me-2" onClick={goBack}>
|
||||
Back
|
||||
</button>
|
||||
<button className="btn btn-primary" onClick={runQuery}>
|
||||
Run
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
{/* Data Table */}
|
||||
<div style={{ maxHeight: "500px", overflow: "auto", marginTop: "1rem" }}>
|
||||
{isLoading ? (
|
||||
<div className="text-center py-4">
|
||||
<div className="spinner-border text-primary" role="status"></div>
|
||||
<p className="mt-2">Loading data...</p>
|
||||
</div>
|
||||
) : error ? (
|
||||
<div className="alert alert-danger">{error}</div>
|
||||
) : (filtered ? filterRows : rows).length === 0 ? (
|
||||
<div className="alert alert-info">No data available</div>
|
||||
) : (
|
||||
<table className="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
{(filtered ? getFilteredHeaders() : getHeaders()).map((header, index) => (
|
||||
<th key={index}>{header}</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{(filtered ? filterRows : rows).map((row, rowIndex) => (
|
||||
<tr key={rowIndex}>
|
||||
{(filtered ? getFilteredHeaders() : getHeaders()).map((header, colIndex) => (
|
||||
<td key={colIndex}>
|
||||
{renderTableCell(row, header)}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReportRunner2Edit;
|
||||
332
src/components/Dashboard/ReportRunner/ReportRunnerAll.js
Normal file
332
src/components/Dashboard/ReportRunner/ReportRunnerAll.js
Normal file
@@ -0,0 +1,332 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { toast } from "react-toastify";
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
import { fetchAllReportsApi } from '../../../APIServices/ReportRunnerAPI';
|
||||
|
||||
|
||||
const ReportRunnerAll = () => {
|
||||
const [gridData, setGridData] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
const [rowSelected, setRowSelected] = useState(null);
|
||||
const [modalDelete, setModalDelete] = useState(false);
|
||||
const [reports, setReports] = useState([]);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
fetchAllReports();
|
||||
}, []);
|
||||
|
||||
// const fetchAllReports = async () => {
|
||||
// setIsLoading(true);
|
||||
// try {
|
||||
// const response = await fetch("/Report_builder/Report_builder"); // Replace with your API URL
|
||||
// const data = await response.json();
|
||||
// setGridData(data);
|
||||
// setReports(data);
|
||||
// if (data.length === 0) setError("No data Available");
|
||||
// } catch (err) {
|
||||
// console.error(err);
|
||||
// setError("Error fetching data.");
|
||||
|
||||
// } finally {
|
||||
// setIsLoading(false);
|
||||
// }
|
||||
// };
|
||||
|
||||
const fetchAllReports = async () => {
|
||||
setIsLoading(true); // Set loading state to true
|
||||
try {
|
||||
const data = await fetchAllReportsApi(); // Call the API to fetch reports
|
||||
console.log("Fetched all reports:", data);
|
||||
|
||||
setGridData(data); // Set the grid data state with the fetched reports
|
||||
setReports(data); // Set the reports state
|
||||
|
||||
if (data.length === 0) {
|
||||
setError("No data available");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error while fetching reports:", error);
|
||||
setError("Error fetching data.");
|
||||
} finally {
|
||||
setIsLoading(false); // Set loading state to false once the request is complete
|
||||
}
|
||||
};
|
||||
|
||||
const goToAdd = () => navigate("/admin/user-report");
|
||||
const goToAdd2 = () => navigate("/admin/reportbuild2all");
|
||||
|
||||
const goToRunner = (report) => {
|
||||
console.log("at time of navigating reportID: ",report.id, " report: ",report);
|
||||
if (report.isSql) {
|
||||
navigate(`/admin/report-runner1/${report.id}`,{ state: { reportId: report.id, reportData: report}});
|
||||
} else {
|
||||
navigate(`/admin/report-runner2/${report.id}`,{ state: { reportId: report.id, reportData: report}}); //reportRunnerEdit.js is called
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (id) => {
|
||||
setModalDelete(false);
|
||||
try {
|
||||
const response = await fetch(`/api/report-builder/${id}`, { method: "DELETE" });
|
||||
if (response.ok) {
|
||||
toast.success("Deleted successfully");
|
||||
fetchAllReports();
|
||||
} else {
|
||||
toast.error("Error deleting data.");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast.error("Error deleting data.");
|
||||
}
|
||||
};
|
||||
|
||||
const openDeleteModal = (row) => {
|
||||
setRowSelected(row);
|
||||
setModalDelete(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
|
||||
{/* Header */}
|
||||
<div className="row">
|
||||
<div className="col-md-4">
|
||||
<h3><b>All Reports</b></h3>
|
||||
</div>
|
||||
<div className="col-md-8 text-end">
|
||||
<button className="btn btn-primary" onClick={goToAdd}>
|
||||
<i className="bi bi-plus"></i> Report Builder SQL
|
||||
</button>
|
||||
<button className="btn btn-primary ms-2" onClick={goToAdd2}>
|
||||
<i className="bi bi-plus"></i> Report Builder URL
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Loading Spinner */}
|
||||
{isLoading && (
|
||||
<div className="alert alert-info mt-3">
|
||||
<div className="spinner-border text-info me-2" role="status" />
|
||||
Loading...
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{/* Report Cards */}
|
||||
{!isLoading && gridData.length > 0 && (
|
||||
<div className="row mt-4">
|
||||
{gridData.map((report, index) => (
|
||||
<div className="col-md-6 col-lg-4 mb-4" key={index}>
|
||||
<div
|
||||
className="card h-100 border-0"
|
||||
onClick={() => goToRunner(report)}
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.3s cubic-bezier(0.165, 0.84, 0.44, 1)',
|
||||
borderRadius: '16px',
|
||||
overflow: 'hidden',
|
||||
backgroundColor: '#ffffff',
|
||||
boxShadow: '0 10px 25px rgba(0,0,0,0.05)',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.transform = 'translateY(-8px)';
|
||||
e.currentTarget.style.boxShadow = '0 15px 30px rgba(0,0,0,0.1)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.transform = 'translateY(0)';
|
||||
e.currentTarget.style.boxShadow = '0 10px 25px rgba(0,0,0,0.05)';
|
||||
}}
|
||||
>
|
||||
{/* Card Header with Gradient */}
|
||||
<div
|
||||
className="d-flex justify-content-between align-items-center p-3"
|
||||
style={{
|
||||
borderBottom: '1px solid rgba(0,0,0,0.05)',
|
||||
background: report.isSql
|
||||
? 'linear-gradient(135deg, #f5f7fa 0%,rgb(198, 218, 255) 100%)'
|
||||
: 'linear-gradient(135deg, #fff9f0 0%,rgb(217, 204, 255) 100%)',
|
||||
height: '50px'
|
||||
}}
|
||||
>
|
||||
<div className="d-flex align-items-center">
|
||||
<div
|
||||
className="me-3 d-flex align-items-center justify-content-center"
|
||||
style={{
|
||||
width: '36px',
|
||||
height: '36px',
|
||||
borderRadius: '10px',
|
||||
background: report.isSql ? 'rgba(65, 105, 225, 0.15)' : 'rgba(255, 165, 0, 0.15)'
|
||||
}}
|
||||
>
|
||||
<i
|
||||
className={`bi ${report.isSql ? 'bi-database' : 'bi-link-45deg'} fs-5`}
|
||||
style={{ color: report.isSql ? '#4169E1' : '#FF8C00' }}
|
||||
></i>
|
||||
</div>
|
||||
<span
|
||||
className="fw-semibold"
|
||||
style={{
|
||||
color: report.isSql ? '#4169E1' : '#FF8C00',
|
||||
fontSize: '0.9rem'
|
||||
}}
|
||||
>
|
||||
{report.isSql == null ? "N/A" : report.isSql ? "SQL Report" : "URL Report"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Status Indicator */}
|
||||
<div
|
||||
className="d-flex align-items-center"
|
||||
style={{
|
||||
backgroundColor: report.active ? 'rgba(40, 167, 69, 0.1)' : 'rgba(220, 53, 69, 0.1)',
|
||||
padding: '6px 12px',
|
||||
borderRadius: '30px'
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: '8px',
|
||||
height: '8px',
|
||||
borderRadius: '50%',
|
||||
backgroundColor: report.active ? '#28a745' : '#dc3545',
|
||||
marginRight: '6px'
|
||||
}}
|
||||
></div>
|
||||
<span
|
||||
style={{
|
||||
fontSize: '0.8rem',
|
||||
fontWeight: '500',
|
||||
color: report.active ? '#28a745' : '#dc3545'
|
||||
}}
|
||||
>
|
||||
{report.active ? "Active" : "Inactive"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Card Body */}
|
||||
<div className="card-body p-4">
|
||||
<h5
|
||||
className="fw-bold mb-3"
|
||||
style={{
|
||||
fontSize: '1.15rem',
|
||||
color: '#343a40',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap'
|
||||
}}
|
||||
title={report.reportName}
|
||||
>
|
||||
{report.reportName}
|
||||
</h5>
|
||||
|
||||
<p
|
||||
style={{
|
||||
display: '-webkit-box',
|
||||
WebkitLineClamp: '2',
|
||||
WebkitBoxOrient: 'vertical',
|
||||
overflow: 'hidden',
|
||||
fontSize: '0.9rem',
|
||||
lineHeight: '1.5',
|
||||
color: '#6c757d',
|
||||
height: '42px',
|
||||
marginBottom: '0'
|
||||
}}
|
||||
title={report.description}
|
||||
>
|
||||
{report.description || "No description available"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Card Footer */}
|
||||
<div
|
||||
className="p-3"
|
||||
style={{
|
||||
borderTop: '1px solid rgba(0,0,0,0.05)',
|
||||
backgroundColor: '#f8f9fa'
|
||||
}}
|
||||
>
|
||||
<div className="d-flex justify-content-between align-items-center">
|
||||
<div className="d-flex align-items-center" style={{ fontSize: '0.75rem', color: '#6c757d' }}>
|
||||
<i className="bi bi-clock me-1"></i>
|
||||
<span>Updated: {new Date(report.updatedAt).toLocaleDateString()}</span>
|
||||
</div>
|
||||
<div
|
||||
className="d-flex align-items-center justify-content-center"
|
||||
style={{
|
||||
width: '32px',
|
||||
height: '32px',
|
||||
borderRadius: '8px',
|
||||
backgroundColor: '#e9ecef',
|
||||
transition: 'all 0.2s ease',
|
||||
color: '#495057'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.backgroundColor = '#dee2e6';
|
||||
e.stopPropagation();
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.backgroundColor = '#e9ecef';
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<i className="bi bi-three-dots"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{/* Error */}
|
||||
{error && <div className="alert alert-danger mt-3">{error}</div>}
|
||||
|
||||
{/* Delete Modal */}
|
||||
{modalDelete && (
|
||||
<div className="modal show d-block" tabIndex="-1" role="dialog">
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h5 className="modal-title">Delete Confirmation</h5>
|
||||
<button
|
||||
type="button"
|
||||
className="btn-close"
|
||||
onClick={() => setModalDelete(false)}
|
||||
/>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<p>Are you sure you want to delete the report?</p>
|
||||
<h2>{rowSelected?.id}</h2>
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary"
|
||||
onClick={() => setModalDelete(false)}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary"
|
||||
onClick={() => handleDelete(rowSelected.id)}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReportRunnerAll;
|
||||
787
src/components/Dashboard/ReportRunner/ReportRunnerEdit.js
Normal file
787
src/components/Dashboard/ReportRunner/ReportRunnerEdit.js
Normal file
@@ -0,0 +1,787 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { fetchStandardParameters, downloadFile } from "../../../APIServices/ReportRunnerAPI";
|
||||
import { toast } from "react-toastify";
|
||||
import './ReportrunnerEdit.css'
|
||||
|
||||
import axios from 'axios';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
const ReportRunnerEdit = () => {
|
||||
const navigate = useNavigate();
|
||||
const { id } = useParams();
|
||||
|
||||
// Report data state
|
||||
const [reportName, setReportName] = useState("Sample Report");
|
||||
const [reportId, setReportId] = useState(id || null);
|
||||
|
||||
// Date parameters state
|
||||
const [dateParam, setDateParam] = useState(true);
|
||||
const [selectDateType, setSelectDateType] = useState("");
|
||||
const [fromDate, setFromDate] = useState("");
|
||||
const [toDate, setToDate] = useState("");
|
||||
const [fromDateQuery, setFromDateQuery] = useState("");
|
||||
const [toDateQuery, setToDateQuery] = useState("");
|
||||
|
||||
// Standard parameters state
|
||||
const [dynamicFields, setDynamicFields] = useState([]);
|
||||
const [dynamicFormValues, setDynamicFormValues] = useState({});
|
||||
|
||||
// Adhoc parameters state
|
||||
const [adhocList, setAdhocList] = useState([]);
|
||||
const [adhocParams, setAdhocParams] = useState([
|
||||
{ andor: "AND", fields_name: "", condition: "=", value: "" },
|
||||
]);
|
||||
|
||||
// Condition options
|
||||
const conditionOptions = ["=", "!=", "<", ">", "<=", ">=", "LIKE", "BETWEEN", "IN"];
|
||||
const andOrOptions = ["AND", "OR", "NOT"];
|
||||
|
||||
// Results state
|
||||
const [rows, setRows] = useState([]);
|
||||
const [filterRows, setFilterRows] = useState([]);
|
||||
const [filtered, setFiltered] = useState(false);
|
||||
const [headers, setHeaders] = useState([]);
|
||||
|
||||
// Query state
|
||||
const [SQLQuery, setSQLQuery] = useState('');
|
||||
const [dateKey, setDateKey] = useState('createdat');
|
||||
const [formattedAdhocParameters, setFormattedAdhocParameters] = useState('');
|
||||
const [selectedValues, setSelectedValues] = useState({});
|
||||
|
||||
// Loading state
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
// Get today's date
|
||||
const todayDate = new Date().toISOString().slice(0, 10);
|
||||
|
||||
useEffect(() => {
|
||||
// Fetch report details
|
||||
if (reportId) {
|
||||
fetchReportDetails(reportId);
|
||||
}
|
||||
|
||||
// Set default date to today
|
||||
handleDateSelect('Today');
|
||||
|
||||
// Run query after a delay to allow data to load
|
||||
const timer = setTimeout(() => {
|
||||
runQuery();
|
||||
}, 2000);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [reportId]);
|
||||
|
||||
const fetchReportDetails = async (id) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
// This API call would need to be implemented
|
||||
const response = await axios.get(`/api/report-builder/${id}`);
|
||||
const data = response.data;
|
||||
|
||||
setReportName(data.reportName);
|
||||
|
||||
// Parse the builder line data
|
||||
const builderLine = data.rpt_builder2_lines?.[0];
|
||||
if (builderLine) {
|
||||
const lineData = JSON.parse(builderLine.model)[0];
|
||||
setAdhocList(lineData.adhoc_param_html || []);
|
||||
setDateParam(lineData.date_param_req || false);
|
||||
setSQLQuery(lineData.url || '');
|
||||
|
||||
// Set dynamic fields from standard parameters
|
||||
setDynamicFields(lineData.std_param_html || []);
|
||||
|
||||
// Initialize dynamic form values
|
||||
const initialFormValues = {};
|
||||
(lineData.std_param_html || []).forEach(field => {
|
||||
initialFormValues[field] = '';
|
||||
});
|
||||
setDynamicFormValues(initialFormValues);
|
||||
|
||||
// Fetch data based on the URL
|
||||
fetchData(lineData.url);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching report details:", error);
|
||||
setError("Error loading report details");
|
||||
toast.error("Failed to load report details");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchData = async (url) => {
|
||||
try {
|
||||
const response = await axios.get(url);
|
||||
const data = response.data?.body ? JSON.parse(response.data.body) : [];
|
||||
setRows(data);
|
||||
setFilterRows(data);
|
||||
|
||||
// Set headers from the first row if available
|
||||
if (data.length > 0) {
|
||||
setHeaders(Object.keys(data[0]));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error);
|
||||
setError("Error loading data");
|
||||
}
|
||||
};
|
||||
|
||||
// Handle changes to adhoc parameters
|
||||
const handleAdhocChange = (index, field, value) => {
|
||||
const updatedParams = [...adhocParams];
|
||||
updatedParams[index][field] = value;
|
||||
setAdhocParams(updatedParams);
|
||||
};
|
||||
|
||||
// Add a new adhoc parameter row
|
||||
const addAdhocRow = () => {
|
||||
const lastRow = adhocParams[adhocParams.length - 1];
|
||||
|
||||
// Only add a new row if the last row has a field name
|
||||
if (lastRow && lastRow.fields_name !== '') {
|
||||
// Format the adhoc parameters for the query
|
||||
let formattedString = '';
|
||||
for (const condition of adhocParams) {
|
||||
const { andor, fields_name, condition: cond, value } = condition;
|
||||
formattedString += ` ${andor} ${fields_name} ${cond} '${value}' `;
|
||||
}
|
||||
setFormattedAdhocParameters(formattedString);
|
||||
|
||||
// Add a new row
|
||||
setAdhocParams([
|
||||
...adhocParams,
|
||||
{ andor: "AND", fields_name: "", condition: "=", value: "" },
|
||||
]);
|
||||
|
||||
// Update selected values for filtering
|
||||
selectColumn(adhocParams);
|
||||
}
|
||||
};
|
||||
|
||||
// Delete an adhoc parameter row
|
||||
const deleteAdhocRow = (index) => {
|
||||
if (adhocParams.length > 1) {
|
||||
// Get the item to be deleted
|
||||
const deletedItem = adhocParams[index];
|
||||
|
||||
// Remove the item from the adhocParams array
|
||||
const updatedParams = adhocParams.filter((_, i) => i !== index);
|
||||
setAdhocParams(updatedParams);
|
||||
|
||||
// Update selected values
|
||||
const updatedSelectedValues = { ...selectedValues };
|
||||
const columnName = deletedItem.fields_name;
|
||||
|
||||
if (updatedSelectedValues[columnName]) {
|
||||
const value = deletedItem.value;
|
||||
const indexInArray = updatedSelectedValues[columnName].indexOf(value);
|
||||
if (indexInArray !== -1) {
|
||||
updatedSelectedValues[columnName].splice(indexInArray, 1);
|
||||
|
||||
// If the array is now empty, remove the property
|
||||
if (updatedSelectedValues[columnName].length === 0) {
|
||||
delete updatedSelectedValues[columnName];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setSelectedValues(updatedSelectedValues);
|
||||
filterRowsBySelectedValues(updatedSelectedValues);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle dynamic form values change
|
||||
const handleDynamicFormChange = (field, value) => {
|
||||
setDynamicFormValues({
|
||||
...dynamicFormValues,
|
||||
[field]: value
|
||||
});
|
||||
};
|
||||
|
||||
// Handle date type selection
|
||||
const handleDateSelect = (dateType) => {
|
||||
setSelectDateType(dateType);
|
||||
setFromDateQuery(null);
|
||||
setToDateQuery(null);
|
||||
|
||||
const currentDate = new Date();
|
||||
let fromDateValue, toDateValue;
|
||||
|
||||
switch (dateType) {
|
||||
case 'Today':
|
||||
fromDateValue = new Date();
|
||||
toDateValue = new Date();
|
||||
break;
|
||||
|
||||
case 'This Week':
|
||||
// Calculate this week (Monday to Sunday)
|
||||
const dayOfWeek = currentDate.getDay();
|
||||
const daysToMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
|
||||
|
||||
fromDateValue = new Date(currentDate);
|
||||
fromDateValue.setDate(currentDate.getDate() - daysToMonday);
|
||||
|
||||
toDateValue = new Date(fromDateValue);
|
||||
toDateValue.setDate(fromDateValue.getDate() + 6);
|
||||
break;
|
||||
|
||||
case 'Last Week':
|
||||
// Calculate last week
|
||||
const lastWeekDayOfWeek = currentDate.getDay();
|
||||
const lastWeekDaysToMonday = lastWeekDayOfWeek === 0 ? 6 : lastWeekDayOfWeek - 1;
|
||||
|
||||
fromDateValue = new Date(currentDate);
|
||||
fromDateValue.setDate(currentDate.getDate() - lastWeekDaysToMonday - 7);
|
||||
|
||||
toDateValue = new Date(fromDateValue);
|
||||
toDateValue.setDate(fromDateValue.getDate() + 6);
|
||||
break;
|
||||
|
||||
case 'This Month':
|
||||
// First day of current month
|
||||
fromDateValue = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1);
|
||||
// Last day of current month
|
||||
toDateValue = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0);
|
||||
break;
|
||||
|
||||
case 'Last Month':
|
||||
// First day of previous month
|
||||
fromDateValue = new Date(currentDate.getFullYear(), currentDate.getMonth() - 1, 1);
|
||||
// Last day of previous month
|
||||
toDateValue = new Date(currentDate.getFullYear(), currentDate.getMonth(), 0);
|
||||
break;
|
||||
|
||||
case 'This Year':
|
||||
// First day of current year
|
||||
fromDateValue = new Date(currentDate.getFullYear(), 0, 1);
|
||||
// Last day of current year
|
||||
toDateValue = new Date(currentDate.getFullYear(), 11, 31);
|
||||
break;
|
||||
|
||||
case 'Last Year':
|
||||
// First day of previous year
|
||||
fromDateValue = new Date(currentDate.getFullYear() - 1, 0, 1);
|
||||
// Last day of previous year
|
||||
toDateValue = new Date(currentDate.getFullYear() - 1, 11, 31);
|
||||
break;
|
||||
|
||||
default:
|
||||
fromDateValue = null;
|
||||
toDateValue = null;
|
||||
}
|
||||
|
||||
if (fromDateValue) {
|
||||
const fromDateString = fromDateValue.toISOString().substring(0, 10);
|
||||
setFromDate(fromDateString);
|
||||
setFromDateQuery(fromDateString);
|
||||
} else {
|
||||
setFromDate('');
|
||||
setFromDateQuery('');
|
||||
}
|
||||
|
||||
if (toDateValue) {
|
||||
const toDateString = toDateValue.toISOString().substring(0, 10);
|
||||
setToDate(toDateString);
|
||||
setToDateQuery(toDateString);
|
||||
} else {
|
||||
setToDate('');
|
||||
setToDateQuery('');
|
||||
}
|
||||
};
|
||||
|
||||
// Run the query
|
||||
const runQuery = () => {
|
||||
console.log("Dynamic form values:", dynamicFormValues);
|
||||
console.log("Date range:", fromDate, toDate);
|
||||
|
||||
let query = SQLQuery || '';
|
||||
|
||||
// Add standard parameters to the query
|
||||
if (Object.keys(dynamicFormValues).length > 0) {
|
||||
Object.keys(dynamicFormValues).forEach((key) => {
|
||||
if (dynamicFormValues[key] !== null && dynamicFormValues[key] !== '') {
|
||||
query += ` AND ${key} = '${dynamicFormValues[key]}'`;
|
||||
}
|
||||
});
|
||||
|
||||
// Update selected values for filtering
|
||||
selectColumn(dynamicFormValues);
|
||||
}
|
||||
|
||||
// Add date range to the query if date parameter is required
|
||||
if (dateParam) {
|
||||
// Determine the correct date key based on adhoc list
|
||||
let tempDateKey = 'createdat';
|
||||
adhocList.forEach(key => {
|
||||
if (key.includes('created_at')) tempDateKey = 'created_at';
|
||||
if (key.includes('createdAt')) tempDateKey = 'createdAt';
|
||||
});
|
||||
setDateKey(tempDateKey);
|
||||
|
||||
if (fromDate && toDate) {
|
||||
const fromDateObj = new Date(fromDate);
|
||||
const toDateObj = new Date(toDate);
|
||||
|
||||
query += ` AND ${tempDateKey} BETWEEN '${fromDateObj.toISOString().split('T')[0]}' AND '${toDateObj.toISOString().split('T')[0]}'`;
|
||||
}
|
||||
}
|
||||
|
||||
// Add adhoc parameters to the query
|
||||
if (formattedAdhocParameters) {
|
||||
query += formattedAdhocParameters;
|
||||
}
|
||||
|
||||
console.log("Final query:", query);
|
||||
|
||||
// Make API request
|
||||
axios.post('/api/report-runner/run', { query })
|
||||
.then((response) => {
|
||||
const data = response.data;
|
||||
|
||||
if (data && data.length > 0) {
|
||||
setRows(data);
|
||||
setFilterRows(data);
|
||||
setHeaders(Object.keys(data[0]));
|
||||
toast.success("Query executed successfully");
|
||||
} else {
|
||||
toast.warning("No data returned");
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error running query:", error);
|
||||
toast.error("Error running the query");
|
||||
});
|
||||
};
|
||||
|
||||
// Export file function
|
||||
const exportFile = (format) => {
|
||||
try {
|
||||
const dataToExport = filtered ? filterRows : rows;
|
||||
const name = reportName.replace(/\s+/g, '_');
|
||||
const timestamp = new Date().toISOString().replace(/[-:T.]/g, '_').slice(0, 17);
|
||||
const fileName = `${name}_${timestamp}`;
|
||||
|
||||
downloadFile(format, dataToExport, fileName);
|
||||
toast.success(`File exported as ${format.toUpperCase()} successfully`);
|
||||
} catch (error) {
|
||||
console.error("Error exporting file:", error);
|
||||
toast.error("Error exporting file");
|
||||
}
|
||||
};
|
||||
|
||||
// Get table headers
|
||||
const getHeaders = () => {
|
||||
if (!rows || rows.length === 0) return [];
|
||||
|
||||
const headerSet = new Set();
|
||||
rows.forEach(row => {
|
||||
Object.keys(row).forEach(key => headerSet.add(key));
|
||||
});
|
||||
|
||||
return Array.from(headerSet);
|
||||
};
|
||||
|
||||
// Get filtered table headers
|
||||
const getFilteredHeaders = () => {
|
||||
if (!filterRows || filterRows.length === 0) return [];
|
||||
|
||||
const headerSet = new Set();
|
||||
filterRows.forEach(row => {
|
||||
Object.keys(row).forEach(key => headerSet.add(key));
|
||||
});
|
||||
|
||||
return Array.from(headerSet);
|
||||
};
|
||||
|
||||
// Check if a value is a date
|
||||
const isDate = (value) => {
|
||||
if (!value) return false;
|
||||
|
||||
if (value instanceof Date) return true;
|
||||
|
||||
if (typeof value === 'object' &&
|
||||
value.year !== undefined &&
|
||||
value.monthValue !== undefined &&
|
||||
value.dayOfMonth !== undefined) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const date = new Date(value);
|
||||
return !isNaN(date.getTime());
|
||||
};
|
||||
|
||||
// Format a date for display
|
||||
const formatDate = (value) => {
|
||||
if (!value) return '';
|
||||
|
||||
if (typeof value === 'object' &&
|
||||
value.year !== undefined &&
|
||||
value.monthValue !== undefined &&
|
||||
value.dayOfMonth !== undefined) {
|
||||
// Handle Java-style date objects
|
||||
const { year, monthValue, dayOfMonth, hour = 0, minute = 0, second = 0 } = value;
|
||||
const date = new Date(year, monthValue - 1, dayOfMonth, hour, minute, second);
|
||||
return date.toLocaleString();
|
||||
}
|
||||
|
||||
// Standard JS date
|
||||
return new Date(value).toLocaleString();
|
||||
};
|
||||
|
||||
// Process data for filtering
|
||||
const selectColumn = (data) => {
|
||||
const newSelectedValues = { ...selectedValues };
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
// Handle array of objects (like adhoc parameters)
|
||||
data.forEach(item => {
|
||||
const { fields_name, value } = item;
|
||||
|
||||
if (fields_name && fields_name.trim() !== '') {
|
||||
if (!newSelectedValues[fields_name]) {
|
||||
newSelectedValues[fields_name] = [];
|
||||
}
|
||||
|
||||
if (value !== null && value.trim() !== '') {
|
||||
if (!newSelectedValues[fields_name].includes(value)) {
|
||||
newSelectedValues[fields_name].push(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (typeof data === 'object') {
|
||||
// Handle object (like dynamic form values)
|
||||
Object.keys(data).forEach(key => {
|
||||
const value = data[key];
|
||||
|
||||
if (!newSelectedValues[key]) {
|
||||
newSelectedValues[key] = [];
|
||||
}
|
||||
|
||||
if (value !== null && value.trim && value.trim() !== '') {
|
||||
if (!newSelectedValues[key].includes(value)) {
|
||||
newSelectedValues[key].push(value);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setSelectedValues(newSelectedValues);
|
||||
filterRowsBySelectedValues(newSelectedValues);
|
||||
};
|
||||
|
||||
// Filter rows based on selected values
|
||||
const filterRowsBySelectedValues = (values = selectedValues) => {
|
||||
const filteredRows = [];
|
||||
|
||||
for (const row of rows) {
|
||||
let isMatch = true;
|
||||
|
||||
// Check each column in the selected values
|
||||
for (const columnName in values) {
|
||||
if (values.hasOwnProperty(columnName) && row.hasOwnProperty(columnName)) {
|
||||
const selectedValuesForColumn = values[columnName];
|
||||
const rowValue = row[columnName];
|
||||
|
||||
if (typeof rowValue === 'boolean') {
|
||||
// Handle boolean values
|
||||
if (selectedValuesForColumn.length === 0) continue;
|
||||
|
||||
const selectedBooleanValue = selectedValuesForColumn[0] === 'true';
|
||||
if (selectedBooleanValue !== rowValue) {
|
||||
isMatch = false;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Handle other data types
|
||||
const convertedValues = selectedValuesForColumn.map(value => {
|
||||
if (typeof rowValue === 'number') {
|
||||
return parseFloat(value);
|
||||
}
|
||||
return value;
|
||||
});
|
||||
|
||||
if (!convertedValues.includes(rowValue)) {
|
||||
isMatch = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check date range if both dates are provided
|
||||
if (fromDateQuery && toDateQuery && isMatch) {
|
||||
const from = new Date(fromDateQuery);
|
||||
const to = new Date(toDateQuery);
|
||||
|
||||
// Set hours to 0 for proper comparison
|
||||
from.setHours(0, 0, 0, 0);
|
||||
to.setHours(23, 59, 59, 999);
|
||||
|
||||
// Get the date from the row using dateKey
|
||||
const rowDate = new Date(row[dateKey]);
|
||||
|
||||
if (rowDate < from || rowDate > to) {
|
||||
isMatch = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isMatch) {
|
||||
filteredRows.push(row);
|
||||
}
|
||||
}
|
||||
|
||||
setFilterRows(filteredRows);
|
||||
|
||||
// Determine if we are filtering or not
|
||||
const isFiltering = Object.values(values).some(arr => arr.length > 0) ||
|
||||
(fromDateQuery && toDateQuery);
|
||||
|
||||
setFiltered(isFiltering);
|
||||
};
|
||||
|
||||
// Render date cell with formatting
|
||||
const renderTableCell = (row, key) => {
|
||||
const isDateField = key === 'createdat' || key === 'createdAt' ||
|
||||
key === 'updated_at' || key === 'updatedAt' ||
|
||||
key === 'created_at' || key === 'creat_at';
|
||||
|
||||
if (isDateField && row[key]) {
|
||||
return formatDate(row[key]);
|
||||
}
|
||||
|
||||
return row[key];
|
||||
};
|
||||
|
||||
// Navigate back to the reports list
|
||||
const goBack = () => {
|
||||
navigate("/admin/report-runner");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
{/* Report Header */}
|
||||
<h4 style={{ fontWeight: 300, display: "inline" }}>
|
||||
<b>Report Name SQL - {reportName}</b>
|
||||
</h4>
|
||||
<hr />
|
||||
|
||||
{/* Date and Standard Parameters Section */}
|
||||
<div className="row">
|
||||
{/* Standard Parameters */}
|
||||
<div className="col-md-6">
|
||||
<h5 style={{ fontWeight: 200, color: "black" }}>
|
||||
<b>Standard Parameters</b>
|
||||
</h5>
|
||||
{dynamicFields.length === 0 ? (
|
||||
<div className="text-danger">No parameter found</div>
|
||||
) : (
|
||||
<div className="row">
|
||||
{dynamicFields.map((field, index) => (
|
||||
<div key={index} className="col-md-6 mb-3">
|
||||
<label>{field}</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={`Enter ${field}`}
|
||||
value={dynamicFormValues[field] || ''}
|
||||
onChange={(e) => handleDynamicFormChange(field, e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Adhoc Parameters Section */}
|
||||
<div className="row mt-4">
|
||||
<div className="col-md-12">
|
||||
<h5 style={{ fontWeight: 200, color: "black" }}>
|
||||
<b>Adhoc Parameters</b>
|
||||
</h5>
|
||||
<table className="table">
|
||||
<tbody>
|
||||
{adhocParams.map((param, index) => (
|
||||
<tr key={index}>
|
||||
<td>
|
||||
<select
|
||||
className="form-select"
|
||||
value={param.andor}
|
||||
onChange={(e) =>
|
||||
handleAdhocChange(index, "andor", e.target.value)
|
||||
}
|
||||
>
|
||||
<option value="">Select Values</option>
|
||||
{andOrOptions.map((option, idx) => (
|
||||
<option key={idx} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<select
|
||||
className="form-select"
|
||||
value={param.fields_name}
|
||||
onChange={(e) =>
|
||||
handleAdhocChange(index, "fields_name", e.target.value)
|
||||
}
|
||||
>
|
||||
<option value="">Select Values</option>
|
||||
{adhocList.map((field, idx) => (
|
||||
<option key={idx} value={field}>
|
||||
{field}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<select
|
||||
className="form-select"
|
||||
value={param.condition}
|
||||
onChange={(e) =>
|
||||
handleAdhocChange(index, "condition", e.target.value)
|
||||
}
|
||||
>
|
||||
<option value="">Select Values</option>
|
||||
{conditionOptions.map((condition, idx) => (
|
||||
<option key={idx} value={condition}>
|
||||
{condition}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
value={param.value}
|
||||
onChange={(e) =>
|
||||
handleAdhocChange(index, "value", e.target.value)
|
||||
}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
className="btn btn-danger me-2"
|
||||
onClick={() => deleteAdhocRow(index)}
|
||||
title="Delete Row"
|
||||
>
|
||||
<i className="bi bi-trash"></i>
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
onClick={addAdhocRow}
|
||||
title="Add Row"
|
||||
>
|
||||
<i className="bi bi-plus"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Report Output Section */}
|
||||
<div className="row mt-4">
|
||||
<div className="col-md-6">
|
||||
<h5 style={{ fontWeight: 300 }}>
|
||||
<b>Report Output</b>
|
||||
</h5>
|
||||
</div>
|
||||
<div className="col-md-6 text-end">
|
||||
{/* Export Dropdown */}
|
||||
<div className="dropdown d-inline-block me-2">
|
||||
<button
|
||||
className="btn btn-outline-primary dropdown-toggle"
|
||||
type="button"
|
||||
id="exportDropdown"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<i className="bi bi-download"></i> Export
|
||||
</button>
|
||||
<ul className="dropdown-menu" aria-labelledby="exportDropdown">
|
||||
<li>
|
||||
<button
|
||||
className="dropdown-item"
|
||||
onClick={() => exportFile("xlsx")}
|
||||
>
|
||||
<i className="bi bi-file-earmark-excel me-2"></i> XLSX
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
className="dropdown-item"
|
||||
onClick={() => exportFile("csv")}
|
||||
>
|
||||
<i className="bi bi-file-earmark-text me-2"></i> CSV
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
className="dropdown-item"
|
||||
onClick={() => exportFile("pdf")}
|
||||
>
|
||||
<i className="bi bi-file-earmark-pdf me-2"></i> PDF
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Back and Run Buttons */}
|
||||
<button className="btn btn-outline-secondary me-2" onClick={goBack}>
|
||||
Back
|
||||
</button>
|
||||
<button className="btn btn-primary" onClick={runQuery}>
|
||||
Run
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
{/* Data Table */}
|
||||
<div style={{ maxHeight: "500px", overflow: "auto", marginTop: "1rem" }}>
|
||||
{isLoading ? (
|
||||
<div className="text-center py-4">
|
||||
<div className="spinner-border text-primary" role="status"></div>
|
||||
<p className="mt-2">Loading data...</p>
|
||||
</div>
|
||||
) : error ? (
|
||||
<div className="alert alert-danger">{error}</div>
|
||||
) : (filtered ? filterRows : rows).length === 0 ? (
|
||||
<div className="alert alert-info">No data available</div>
|
||||
) : (
|
||||
<table className="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
{(filtered ? getFilteredHeaders() : getHeaders()).map((header, index) => (
|
||||
<th key={index}>{header}</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{(filtered ? filterRows : rows).map((row, rowIndex) => (
|
||||
<tr key={rowIndex}>
|
||||
{(filtered ? getFilteredHeaders() : getHeaders()).map((header, colIndex) => (
|
||||
<td key={colIndex}>
|
||||
{renderTableCell(row, header)}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReportRunnerEdit;
|
||||
20
src/components/Dashboard/ReportRunner/ReportrunnerEdit.css
Normal file
20
src/components/Dashboard/ReportRunner/ReportrunnerEdit.css
Normal file
@@ -0,0 +1,20 @@
|
||||
/* Grid for dropdown content */
|
||||
.dropdown-menu {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dropdown-menu.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Custom grid layout inside dropdown */
|
||||
.grid-template-columns-3 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr); /* 3 columns */
|
||||
gap: 0.5rem; /* Spacing between buttons */
|
||||
}
|
||||
|
||||
.d-grid button {
|
||||
width: 100%; /* Make buttons span the grid cell */
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user