import * as React from 'react'; import { Hook } from '../../hook'; import { en } from './(translation)/en'; import { es } from './(translation)/es'; import { excelDateToJSDate, lastMonthDate } from 'sync-ql'; import { Dialog } from '../dialog'; import { Button, MenuItem, Table, TableBody, TableRow, Typography, Checkbox, FormControlLabel, } from '@mui/material'; import { DatePicker } from '@mui/x-date-pickers'; import { ExcelFiles, readExcelFiles } from '../../utils/excel'; import { Loading } from '../../loading'; import { ExcelUploadContainer, ExcelUploadLeft, ExcelUploadLeftContent, ExcelUploadContent, ExcelUploadContentRow, ExcelUploadRowTextField, ExcelUploadCellTextField, ExcelUploadButton, ExcelUploadRight, ExcelUploadCell, ExcelUploadTable } from './style'; type ColumnType = 'text' | 'number' | 'date' | 'boolean' | 'text?' | 'number?' | 'date?' | 'boolean?'; type FileColumnName = string; export interface ColumnsCrosswalk { headersLine: number, dateColumn?: string, columns: { [columnName: string]: [ColumnType, FileColumnName, FileColumnName?, FileColumnName?, FileColumnName?, FileColumnName?, FileColumnName?, FileColumnName?, FileColumnName?, FileColumnName?, FileColumnName?], }, } export interface ExcelUploadData { content?: { date?: 'year' | 'date' | 'month' | 'hide', label?: string, applyToDifference?: boolean, crosswalk?: ColumnsCrosswalk }, } export interface ExcelUploadOutput { files?: ExcelFiles, fileDate?: Date, applyToDifference?: boolean, crosswalkChanged?: boolean; } export const ExcelUploadDialog = Hook.Dialog(({ data, open, onClose }) => { const [located] = Hook.useLocalization({ en, es }); const hiddenFileInput = React.useRef(null); const [progress, setProgress] = React.useState(0); const [loading, setLoading] = React.useState(false); const [importing, setImporting] = React.useState(false); const [uploadedFiles, setUploadedFiles] = React.useState(null); const [files, setFiles] = React.useState(null); const [fileDate, setFileDate] = React.useState( data.content ? data.content.date === 'month' ? new Date(new Date().getFullYear(), new Date().getMonth(), 1) : data.content.date === 'year' ? new Date(new Date().getFullYear(), 0, 1) : new Date() : new Date() ); const [applyToDifference, setApplyToDifference] = React.useState(true); const [headersLine, setHeadersLine] = React.useState( data.content && data.content.crosswalk && data.content.crosswalk.headersLine > 0 ? data.content.crosswalk.headersLine : 1 ); const [headers, setHeaders] = React.useState() const [crosswalk, setCrosswalk] = React.useState<{ value: string, required: boolean }[]>(); const [delimiter, setDelimiter] = React.useState(','); const handleClose = () => { const crosswalkChanged = overrideDataCrosswalk(); onClose({ data: { files: updateFile(), fileDate: fileDate, applyToDifference, crosswalkChanged, }, rejected: false }) } const handleInputFileChange = (event: any) => { const uploadedFiles = event.target.files && event.target.files.length > 0 ? event.target.files : null; if (uploadedFiles) { setUploadedFiles(uploadedFiles); importFile(uploadedFiles, delimiter); } } const importFile = (uploadedFiles: FileList, delimiter: string) => { setFiles(null) setProgress(1) setLoading(true); setTimeout(() => { readExcelFiles(uploadedFiles, undefined, 1, delimiter, progress => { setProgress(Math.max(1, progress)) }) .then((files: ExcelFiles) => { if (files) { setFiles(files) updateHeaders(headersLine, files) } }) .finally(() => setLoading(false)) }, 100) } const handleColumnChange = (index: number) => (event: any) => { crosswalk[index] = { value: event.target.value, required: crosswalk[index].required, }; setCrosswalk([...crosswalk]); } const updateHeaders = (headersLine: number, files: ExcelFiles) => { if (files && data.content && data.content.crosswalk) { for (const fileName in files) { if (Object.prototype.hasOwnProperty.call(files, fileName)) { const file = files[fileName]; if (file) { for (const sheetName in file) { if (Object.prototype.hasOwnProperty.call(file, sheetName)) { const sheet = file[sheetName]; if (sheet) { headersLine = Math.min(headersLine, sheet.length); if (headersLine > 0) { const fileHeaders = [...sheet[headersLine - 1]]; { for (let i = fileHeaders.length - 1; i >= 0; i--) { fileHeaders[i] = `${fileHeaders[i]}`.toLowerCase().replace(/\s+/gm, ' ').trim(); if (!fileHeaders[i]) { fileHeaders.splice(i, 1) } } } setHeaders(fileHeaders); updateCrosswalk(fileHeaders); } break; } } } break; } } } } } const updateCrosswalk = (headers: string[]) => { if (headers && data.content && data.content.crosswalk && data.content.crosswalk.columns) { const crosswalk = []; for (const columnName in data.content.crosswalk.columns) { if (Object.prototype.hasOwnProperty.call(data.content.crosswalk.columns, columnName)) { const column = data.content.crosswalk.columns[columnName]; let found = false; for (let c = 1; c < column.length; c++) { const headerName = `${column[c]}`.toLowerCase().replace(/\s+/gm, ' ').trim() if (found = !!headers.find(h => h === headerName)) { crosswalk.push({ value: headerName, required: !column[0].endsWith('?') }) break; } } if (!found) { crosswalk.push({ value: '', required: false }) } } } setCrosswalk(crosswalk); } } const overrideDataCrosswalk = () => { let result = false; if (crosswalk && data.content && data.content.crosswalk && data.content.crosswalk.columns) { if (data.content.crosswalk.headersLine !== headersLine) { data.content.crosswalk.headersLine = headersLine; result = true; } Object.keys(data.content.crosswalk.columns).map((columnName, index) => { let found = false; const column = data.content.crosswalk.columns[columnName]; const headerName = `${crosswalk[index].value}`.toLowerCase().replace(/\s+/gm, ' ').trim() for (let c = 1; c < column.length; c++) { if (found = (column[c] === headerName)) { break; } } if (!found && !!headerName) { column.push(headerName); if (column.length > 11) { column.splice(1, 1) } result = true; } }) } return result; } const getFormattedDateGlobal = (date: Date, delimiter: string = '/') => { var year = date.getFullYear(); var month = (1 + date.getMonth()).toString(); month = month.length > 1 ? month : '0' + month; var day = date.getDate().toString(); day = day.length > 1 ? day : '0' + day; return year + delimiter + month + delimiter + day; } const updateFile = () => { if (files && crosswalk && data.content && data.content.crosswalk && data.content.crosswalk.columns) { setImporting(true); for (const fileName in files) { if (Object.prototype.hasOwnProperty.call(files, fileName)) { const file = files[fileName]; if (file) { for (const sheetName in file) { if (Object.prototype.hasOwnProperty.call(file, sheetName)) { const sheet = file[sheetName]; if (sheet) { const columns = Object.values(data.content.crosswalk.columns); const columnNames = Object.keys(data.content.crosswalk.columns); const headersLineNumber = Math.min(headersLine, sheet.length); if (headersLineNumber > 0) { if (headersLineNumber > 1) { sheet.splice(0, headersLineNumber - 1); } const fileHeaders = sheet[0]; const fileColumnIndices: number[] = []; const hasDateColumn = !!data.content.crosswalk.dateColumn; const dateColumnIndex = hasDateColumn ? columnNames.findIndex(x => x === data.content.crosswalk.dateColumn) : -1; const needDateColumn = hasDateColumn && dateColumnIndex < 0; for (let c = 0; c < fileHeaders.length; c++) { fileHeaders[c] = `${fileHeaders[c]}`.toLowerCase().replace(/\s+/gm, ' ').trim(); if (fileHeaders[c]) { const fileColumnIndex = crosswalk.findIndex(x => x.value === fileHeaders[c]); { fileColumnIndices.push(fileColumnIndex); } if (fileColumnIndex >= 0) { fileHeaders[c] = columnNames[fileColumnIndex]; } } else fileColumnIndices.push(-1); } if (needDateColumn) { fileHeaders.unshift(data.content.crosswalk.dateColumn) } for (let r = 1; r < sheet.length; r++) { const row = sheet[r]; for (let c = 0; c < row.length; c++) { if (fileColumnIndices[c] >= 0) { const value = row[c]; const column = columns[fileColumnIndices[c]]; switch (column[0]) { case 'date': case 'date?': { if (typeof value === 'string') { if (/^\d{1,2}(\/|-)\d{1,2}(\/|-)\d{4}$/.test(value)) { // MM-dd-yyyy const split = value.split(/(\/|-)/); row[c] = getFormattedDateGlobal(new Date(Number(split[2]), Number(split[0]) - 1, Number(split[1])), '-') } else if (/^\d{4}(\/|-)\d{1,2}(\/|-)\d{1,2}$/.test(value)) { // yyyy-MM-dd const split = value.split(/(\/|-)/); row[c] = getFormattedDateGlobal(new Date(Number(split[0]), Number(split[1]) - 1, Number(split[2])), '-') } else if (/^\d{8}$/.test(value)) { // yyyyMMdd row[c] = getFormattedDateGlobal(new Date(Number(value.substring(0, 4)), Number(value.substring(4, 6)) - 1, Number(value.substring(6, 8))), '-') } else if (/^\d{6}$/.test(value)) { // yyyyMM row[c] = getFormattedDateGlobal(new Date(Number(value.substring(0, 4)), Number(value.substring(4, 6)) - 1, 1), '-') } else row[c] = ''; } else if (typeof value === 'number') { row[c] = getFormattedDateGlobal(excelDateToJSDate(value), '-') } else row[c] = ''; } break; case 'number': case 'number?': { if (typeof value === 'string') { if (value.startsWith('$')) { row[c] = Number(value.substring(1, value.length).replace(',', '')) } else if (value.endsWith('$')) { row[c] = Number(value.substring(0, value.length - 1).replace(',', '')) } else { row[c] = Number(value.replace(',', '')) } } else if (typeof value !== 'number') { row[c] = 0; } } break; case 'boolean': case 'boolean?': { if (typeof value === 'string') { const v = value.toLowerCase(); row[c] = v === 'y' || v === 'yes' || v === '1'; } else if (typeof value === 'number') { row[c] = value !== 0; } else row[c] = !!row[c] } break; default: { row[c] = value ? `${value}` : '' } break; } } } if (needDateColumn) { const date = lastMonthDate(fileDate); row.unshift(`${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`) } setProgress(Math.round(10000 * r / sheet.length) / 100) } } break; } } } break; } } } setImporting(false); } return files; } const getColumnName = (columnName: string, index: number) => { if (crosswalk) { if (crosswalk.length > index) { if (!crosswalk[index].required) { return `${columnName}?` } } } return columnName; } const label = data.content && data.content.label ? ` ${data.content.label}` : ''; return ( onClose({ data: {}, rejected: true })} actions={ }> { uploadedFiles && uploadedFiles.length > 0 && uploadedFiles[0].name.toLowerCase().endsWith('.csv') && { setDelimiter(e.target.value) importFile(uploadedFiles, e.target.value) }}> {located.dialog.comma} {located.dialog.tab} {located.dialog.semicolon} } { data.content && data.content.crosswalk && { const value = Number(e.target.value) ? Number(e.target.value) : 1; setHeadersLine(value) updateHeaders(value, files) }} /> } { data.content && data.content.date && data.content.date !== 'hide' && ( ) } disabled={loading || importing} value={fileDate} onChange={(e: any) => { setFileDate( data.content.date === 'month' ? new Date(e.getFullYear(), e.getMonth(), 1) : data.content.date === 'year' ? new Date(e.getFullYear(), 0, 1) : e ) }}> } { data.content && data.content.applyToDifference && { setApplyToDifference(e.target.checked) }} color="primary" disabled={loading || importing} /> } label={located.dialog.applyToDifference} /> } { uploadedFiles && uploadedFiles.length > 0 ? uploadedFiles.length === 1 ? uploadedFiles[0].name : `${uploadedFiles.length} ${located.dialog.selectedFiles}` : '' } hiddenFileInput.current.click()}> { loading ? : located.dialog.select } { data.content && data.content.crosswalk && Object.keys(data.content.crosswalk.columns).map((columnName, index) => ( {getColumnName(columnName, index)} 0} variant="standard" disabled={!files || loading || importing} value={`${crosswalk && crosswalk.length > index ? crosswalk[index].value : ''}`} onChange={handleColumnChange(index)} > { headers && headers.map(option => ( {option} )) } )) }
); }) export default ExcelUploadDialog;