|
|
@@ -0,0 +1,302 @@ |
|
|
|
//src\components\ReportSearchBox3\SearchBox3.tsx |
|
|
|
"use client"; |
|
|
|
|
|
|
|
import Grid from "@mui/material/Grid"; |
|
|
|
import Card from "@mui/material/Card"; |
|
|
|
import CardContent from "@mui/material/CardContent"; |
|
|
|
import Typography from "@mui/material/Typography"; |
|
|
|
import React, { useCallback, useMemo, useState } from "react"; |
|
|
|
import { useTranslation } from "react-i18next"; |
|
|
|
import TextField from "@mui/material/TextField"; |
|
|
|
import FormControl from "@mui/material/FormControl"; |
|
|
|
import InputLabel from "@mui/material/InputLabel"; |
|
|
|
import Select, { SelectChangeEvent } from "@mui/material/Select"; |
|
|
|
import MenuItem from "@mui/material/MenuItem"; |
|
|
|
import CardActions from "@mui/material/CardActions"; |
|
|
|
import Button from "@mui/material/Button"; |
|
|
|
import RestartAlt from "@mui/icons-material/RestartAlt"; |
|
|
|
import Search from "@mui/icons-material/Search"; |
|
|
|
import dayjs from "dayjs"; |
|
|
|
import "dayjs/locale/zh-hk"; |
|
|
|
import { DatePicker } from "@mui/x-date-pickers/DatePicker"; |
|
|
|
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; |
|
|
|
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; |
|
|
|
import { Box } from "@mui/material"; |
|
|
|
import * as XLSX from 'xlsx-js-style'; |
|
|
|
//import { DownloadReportButton } from '../LateStartReportGen/DownloadReportButton'; |
|
|
|
|
|
|
|
interface BaseCriterion<T extends string> { |
|
|
|
label: string; |
|
|
|
label2?: string; |
|
|
|
paramName: T; |
|
|
|
paramName2?: T; |
|
|
|
} |
|
|
|
|
|
|
|
interface TextCriterion<T extends string> extends BaseCriterion<T> { |
|
|
|
type: "text"; |
|
|
|
} |
|
|
|
|
|
|
|
interface SelectCriterion<T extends string> extends BaseCriterion<T> { |
|
|
|
type: "select"; |
|
|
|
options: string[]; |
|
|
|
} |
|
|
|
|
|
|
|
interface DateRangeCriterion<T extends string> extends BaseCriterion<T> { |
|
|
|
type: "dateRange"; |
|
|
|
} |
|
|
|
|
|
|
|
export type Criterion<T extends string> = |
|
|
|
| TextCriterion<T> |
|
|
|
| SelectCriterion<T> |
|
|
|
| DateRangeCriterion<T>; |
|
|
|
|
|
|
|
interface Props<T extends string> { |
|
|
|
criteria: Criterion<T>[]; |
|
|
|
onSearch: (inputs: Record<T, string>) => void; |
|
|
|
onReset?: () => void; |
|
|
|
} |
|
|
|
|
|
|
|
function SearchBox<T extends string>({ |
|
|
|
criteria, |
|
|
|
onSearch, |
|
|
|
onReset, |
|
|
|
}: Props<T>) { |
|
|
|
const { t } = useTranslation("common"); |
|
|
|
const defaultInputs = useMemo( |
|
|
|
() => |
|
|
|
criteria.reduce<Record<T, string>>( |
|
|
|
(acc, c) => { |
|
|
|
return { ...acc, [c.paramName]: c.type === "select" ? "All" : "" }; |
|
|
|
}, |
|
|
|
{} as Record<T, string>, |
|
|
|
), |
|
|
|
[criteria], |
|
|
|
); |
|
|
|
const [inputs, setInputs] = useState(defaultInputs); |
|
|
|
|
|
|
|
const makeInputChangeHandler = useCallback( |
|
|
|
(paramName: T): React.ChangeEventHandler<HTMLInputElement> => { |
|
|
|
return (e) => { |
|
|
|
setInputs((i) => ({ ...i, [paramName]: e.target.value })); |
|
|
|
}; |
|
|
|
}, |
|
|
|
[], |
|
|
|
); |
|
|
|
|
|
|
|
const makeSelectChangeHandler = useCallback((paramName: T) => { |
|
|
|
return (e: SelectChangeEvent) => { |
|
|
|
setInputs((i) => ({ ...i, [paramName]: e.target.value })); |
|
|
|
}; |
|
|
|
}, []); |
|
|
|
|
|
|
|
const makeDateChangeHandler = useCallback((paramName: T) => { |
|
|
|
return (e: any) => { |
|
|
|
setInputs((i) => ({ ...i, [paramName]: dayjs(e).format("YYYY-MM-DD") })); |
|
|
|
}; |
|
|
|
}, []); |
|
|
|
|
|
|
|
const makeDateToChangeHandler = useCallback((paramName: T) => { |
|
|
|
return (e: any) => { |
|
|
|
setInputs((i) => ({ |
|
|
|
...i, |
|
|
|
[paramName + "To"]: dayjs(e).format("YYYY-MM-DD"), |
|
|
|
})); |
|
|
|
}; |
|
|
|
}, []); |
|
|
|
|
|
|
|
const handleReset = () => { |
|
|
|
setInputs(defaultInputs); |
|
|
|
onReset?.(); |
|
|
|
}; |
|
|
|
|
|
|
|
const handleSearch = () => { |
|
|
|
onSearch(inputs); |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
const handleDownload = async () => { |
|
|
|
//setIsLoading(true); |
|
|
|
|
|
|
|
try { |
|
|
|
const response = await fetch('/temp/AR04_Cost and Expense Report.xlsx', { |
|
|
|
headers: { |
|
|
|
'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', |
|
|
|
}, |
|
|
|
}); |
|
|
|
if (!response.ok) throw new Error('Network response was not ok.'); |
|
|
|
|
|
|
|
const data = await response.blob(); |
|
|
|
const reader = new FileReader(); |
|
|
|
reader.onload = (e) => { |
|
|
|
if (e.target && e.target.result) { |
|
|
|
const ab = e.target.result as ArrayBuffer; |
|
|
|
const workbook = XLSX.read(ab, { type: 'array' }); |
|
|
|
const firstSheetName = workbook.SheetNames[0]; |
|
|
|
const worksheet = workbook.Sheets[firstSheetName]; |
|
|
|
|
|
|
|
// Add the current date to cell C2 |
|
|
|
const cellAddress = 'C2'; |
|
|
|
const date = new Date().toISOString().split('T')[0]; // Format YYYY-MM-DD |
|
|
|
const formattedDate = date.replace(/-/g, '/'); // Change format to YYYY/MM/DD |
|
|
|
XLSX.utils.sheet_add_aoa(worksheet, [[formattedDate]], { origin: cellAddress }); |
|
|
|
|
|
|
|
// Calculate the maximum length of content in each column and set column width |
|
|
|
const colWidths: number[] = []; |
|
|
|
|
|
|
|
const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: "", blankrows: true }) as (string | number)[][]; |
|
|
|
jsonData.forEach((row: (string | number)[]) => { |
|
|
|
row.forEach((cell: string | number, index: number) => { |
|
|
|
const valueLength = cell.toString().length; |
|
|
|
colWidths[index] = Math.max(colWidths[index] || 0, valueLength); |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
// Apply calculated widths to each column, skipping column A |
|
|
|
worksheet['!cols'] = colWidths.map((width, index) => { |
|
|
|
if (index === 0) { |
|
|
|
return { wch: 8 }; // Set default or specific width for column A if needed |
|
|
|
} |
|
|
|
return { wch: width + 2 }; // Add padding to width |
|
|
|
}); |
|
|
|
|
|
|
|
// Style for cell A1: Font size 16 and bold |
|
|
|
if (worksheet['A1']) { |
|
|
|
worksheet['A1'].s = { |
|
|
|
font: { |
|
|
|
bold: true, |
|
|
|
sz: 16, // Font size 16 |
|
|
|
//name: 'Times New Roman' // Specify font |
|
|
|
} |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
// Apply styles from A2 to A4 (bold) |
|
|
|
['A2', 'A3', 'A4'].forEach(cell => { |
|
|
|
if (worksheet[cell]) { |
|
|
|
worksheet[cell].s = { font: { bold: true } }; |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
// Formatting from A6 to J6 |
|
|
|
// Apply styles from A6 to J6 (bold, bottom border, center alignment) |
|
|
|
for (let col = 0; col < 10; col++) { // Columns A to K |
|
|
|
const cellRef = XLSX.utils.encode_col(col) + '6'; |
|
|
|
if (worksheet[cellRef]) { |
|
|
|
worksheet[cellRef].s = { |
|
|
|
font: { bold: true }, |
|
|
|
alignment: { horizontal: 'center' }, |
|
|
|
border: { |
|
|
|
bottom: { style: 'thin', color: { auto: 1 } } |
|
|
|
} |
|
|
|
}; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Format filename with date |
|
|
|
const today = new Date().toISOString().split('T')[0].replace(/-/g, '_'); // Get current date and format as YYYY_MM_DD |
|
|
|
const filename = `AR04_Cost_and_Expense_Report_${today}.xlsx`; // Append formatted date to the filename |
|
|
|
|
|
|
|
// Convert workbook back to XLSX file |
|
|
|
XLSX.writeFile(workbook, filename); |
|
|
|
} else { |
|
|
|
throw new Error('Failed to load file'); |
|
|
|
} |
|
|
|
}; |
|
|
|
reader.readAsArrayBuffer(data); |
|
|
|
} catch (error) { |
|
|
|
console.error('Error downloading the file: ', error); |
|
|
|
} |
|
|
|
|
|
|
|
//setIsLoading(false); |
|
|
|
}; |
|
|
|
return ( |
|
|
|
<Card> |
|
|
|
<CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}> |
|
|
|
<Typography variant="overline">{t("Search Criteria")}</Typography> |
|
|
|
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> |
|
|
|
{criteria.map((c) => { |
|
|
|
return ( |
|
|
|
<Grid key={c.paramName} item xs={6}> |
|
|
|
{c.type === "text" && ( |
|
|
|
<TextField |
|
|
|
label={c.label} |
|
|
|
fullWidth |
|
|
|
onChange={makeInputChangeHandler(c.paramName)} |
|
|
|
value={inputs[c.paramName]} |
|
|
|
/> |
|
|
|
)} |
|
|
|
{c.type === "select" && ( |
|
|
|
<FormControl fullWidth> |
|
|
|
<InputLabel>{c.label}</InputLabel> |
|
|
|
<Select |
|
|
|
label={c.label} |
|
|
|
onChange={makeSelectChangeHandler(c.paramName)} |
|
|
|
value={inputs[c.paramName]} |
|
|
|
> |
|
|
|
<MenuItem value={"All"}>{t("All")}</MenuItem> |
|
|
|
{c.options.map((option, index) => ( |
|
|
|
<MenuItem key={`${option}-${index}`} value={option}> |
|
|
|
{option} |
|
|
|
</MenuItem> |
|
|
|
))} |
|
|
|
</Select> |
|
|
|
</FormControl> |
|
|
|
)} |
|
|
|
{c.type === "dateRange" && ( |
|
|
|
<LocalizationProvider |
|
|
|
dateAdapter={AdapterDayjs} |
|
|
|
// TODO: Should maybe use a custom adapterLocale here to support YYYY-MM-DD |
|
|
|
adapterLocale="zh-hk" |
|
|
|
> |
|
|
|
<Box display="flex"> |
|
|
|
<FormControl fullWidth> |
|
|
|
<DatePicker |
|
|
|
label={c.label} |
|
|
|
onChange={makeDateChangeHandler(c.paramName)} |
|
|
|
value={inputs[c.paramName] ? dayjs(inputs[c.paramName]) : null} |
|
|
|
/> |
|
|
|
</FormControl> |
|
|
|
<Box |
|
|
|
display="flex" |
|
|
|
alignItems="center" |
|
|
|
justifyContent="center" |
|
|
|
marginInline={2} |
|
|
|
> |
|
|
|
{"-"} |
|
|
|
</Box> |
|
|
|
<FormControl fullWidth> |
|
|
|
<DatePicker |
|
|
|
label={c.label2} |
|
|
|
onChange={makeDateToChangeHandler(c.paramName)} |
|
|
|
value={inputs[c.paramName.concat("To") as T] ? dayjs(inputs[c.paramName.concat("To") as T]) : null} |
|
|
|
/> |
|
|
|
</FormControl> |
|
|
|
</Box> |
|
|
|
</LocalizationProvider> |
|
|
|
)} |
|
|
|
</Grid> |
|
|
|
); |
|
|
|
})} |
|
|
|
</Grid> |
|
|
|
<CardActions sx={{ justifyContent: "flex-end" }}> |
|
|
|
<Button |
|
|
|
variant="text" |
|
|
|
startIcon={<RestartAlt />} |
|
|
|
onClick={handleReset} |
|
|
|
> |
|
|
|
{t("Reset")} |
|
|
|
</Button> |
|
|
|
<Button |
|
|
|
variant="outlined" |
|
|
|
startIcon={<Search />} |
|
|
|
onClick={handleSearch} |
|
|
|
> |
|
|
|
{t("Search")} |
|
|
|
</Button> |
|
|
|
</CardActions> |
|
|
|
</CardContent> |
|
|
|
</Card> |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
export default SearchBox; |