|
|
@@ -0,0 +1,283 @@ |
|
|
|
"use client" |
|
|
|
|
|
|
|
import { TeamResult } from "@/app/api/team"; |
|
|
|
import { CompanyTeamCashFlow, fetchCompanyTeamCashFlow } from "@/app/api/teamCashflow"; |
|
|
|
import { Autocomplete, Card, CardContent, CardHeader, FormControl, Grid, InputLabel, MenuItem, Select, TextField, Typography } from "@mui/material" |
|
|
|
import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers"; |
|
|
|
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; |
|
|
|
import { ApexOptions } from "apexcharts"; |
|
|
|
import dayjs, { Dayjs } from "dayjs"; |
|
|
|
import { useCallback, useEffect, useState } from "react"; |
|
|
|
import ReactApexChart from "react-apexcharts"; |
|
|
|
import { useTranslation } from "react-i18next"; |
|
|
|
|
|
|
|
type Props = { |
|
|
|
cashflow: CompanyTeamCashFlow[] |
|
|
|
teams: TeamResult[] |
|
|
|
} |
|
|
|
|
|
|
|
type Filter = { |
|
|
|
year: Dayjs, |
|
|
|
teamId: number, |
|
|
|
} |
|
|
|
const CompanyTeamCashFlowV2: React.FC<Props> = ({ |
|
|
|
cashflow, |
|
|
|
teams |
|
|
|
}) => { |
|
|
|
const currYear = dayjs() |
|
|
|
const { |
|
|
|
t, |
|
|
|
i18n: { language }, |
|
|
|
} = useTranslation(); |
|
|
|
const [filter, setFilter] = useState<Filter>({ |
|
|
|
year: currYear, |
|
|
|
teamId: 0 |
|
|
|
}) |
|
|
|
const emptyList = new Array(12).fill(0) |
|
|
|
const [needFetch, setNeedFetch] = useState(false); |
|
|
|
const [leftMax, setLeftMax] = useState(1000000); |
|
|
|
const [rightMax, setRightMax] = useState(1000000); |
|
|
|
const [incomeList, setIncomeList] = useState([...emptyList]); |
|
|
|
const [cumulativeIncomeList, setCumulativeIncomeList] = useState([...emptyList]); |
|
|
|
const [expenditureList, setExpenditureList] = useState([...emptyList]); |
|
|
|
const [cumulativeExpenditureList, setCumulativeExpenditureList] = useState([...emptyList]); |
|
|
|
|
|
|
|
const options: ApexOptions = { |
|
|
|
tooltip: { |
|
|
|
y: { |
|
|
|
formatter: function (val) { |
|
|
|
return val.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }); |
|
|
|
} |
|
|
|
} |
|
|
|
}, |
|
|
|
chart: { |
|
|
|
height: 350, |
|
|
|
type: "line", |
|
|
|
}, |
|
|
|
stroke: { |
|
|
|
width: [0, 0, 2, 2], |
|
|
|
}, |
|
|
|
plotOptions: { |
|
|
|
bar: { |
|
|
|
horizontal: false, |
|
|
|
distributed: false, |
|
|
|
}, |
|
|
|
}, |
|
|
|
dataLabels: { |
|
|
|
enabled: false, |
|
|
|
}, |
|
|
|
xaxis: { |
|
|
|
categories: [ |
|
|
|
t("JAN"), |
|
|
|
t("FEB"), |
|
|
|
t("MAR"), |
|
|
|
t("APR"), |
|
|
|
t("MAY"), |
|
|
|
t("JUN"), |
|
|
|
t("JUL"), |
|
|
|
t("AUG"), |
|
|
|
t("SEP"), |
|
|
|
t("OCT"), |
|
|
|
t("NOV"), |
|
|
|
t("DEC"), |
|
|
|
], |
|
|
|
}, |
|
|
|
yaxis: [ |
|
|
|
{ |
|
|
|
title: { |
|
|
|
text:t("Monthly Income and Expenditure (HKD)"), |
|
|
|
style: { |
|
|
|
fontSize: '15px' |
|
|
|
} |
|
|
|
}, |
|
|
|
min: 0, |
|
|
|
max: leftMax, |
|
|
|
tickAmount: 5, |
|
|
|
labels: { |
|
|
|
formatter: function (val) { |
|
|
|
return val.toLocaleString() |
|
|
|
} |
|
|
|
} |
|
|
|
}, |
|
|
|
{ |
|
|
|
show: false, |
|
|
|
seriesName: "Monthly Expenditure", |
|
|
|
title: { |
|
|
|
text: t("Monthly Expenditure (HKD)"), |
|
|
|
}, |
|
|
|
min: 0, |
|
|
|
max: leftMax, |
|
|
|
tickAmount: 5, |
|
|
|
}, |
|
|
|
{ |
|
|
|
seriesName: "Cumulative Income", |
|
|
|
opposite: true, |
|
|
|
title: { |
|
|
|
text: t("Cumulative Income and Expenditure (HKD)"), |
|
|
|
style: { |
|
|
|
fontSize: '15px' |
|
|
|
} |
|
|
|
}, |
|
|
|
min: 0, |
|
|
|
max: rightMax, |
|
|
|
tickAmount: 5, |
|
|
|
labels: { |
|
|
|
formatter: function (val) { |
|
|
|
return val.toLocaleString() |
|
|
|
} |
|
|
|
} |
|
|
|
}, |
|
|
|
{ |
|
|
|
show: false, |
|
|
|
seriesName: "Cumulative Expenditure", |
|
|
|
opposite: true, |
|
|
|
title: { |
|
|
|
text: t("Cumulative Expenditure (HKD)"), |
|
|
|
}, |
|
|
|
min: 0, |
|
|
|
max: rightMax, |
|
|
|
tickAmount: 5, |
|
|
|
}, |
|
|
|
], |
|
|
|
grid: { |
|
|
|
borderColor: "#f1f1f1", |
|
|
|
}, |
|
|
|
annotations: {}, |
|
|
|
series: [ |
|
|
|
{ |
|
|
|
name: t("Monthly Income"), |
|
|
|
type: "column", |
|
|
|
color: "#ffde91", |
|
|
|
data: incomeList, |
|
|
|
}, |
|
|
|
{ |
|
|
|
name: t("Monthly Expenditure"), |
|
|
|
type: "column", |
|
|
|
color: "#82b59a", |
|
|
|
data: expenditureList, |
|
|
|
}, |
|
|
|
{ |
|
|
|
name: t("Cumulative Income"), |
|
|
|
type: "line", |
|
|
|
color: "#EE6D7A", |
|
|
|
data: cumulativeIncomeList, |
|
|
|
}, |
|
|
|
{ |
|
|
|
name: t("Cumulative Expenditure"), |
|
|
|
type: "line", |
|
|
|
color: "#7cd3f2", |
|
|
|
data: cumulativeExpenditureList, |
|
|
|
}, |
|
|
|
], |
|
|
|
}; |
|
|
|
const fetchData = useCallback(async(filter: Filter) => { |
|
|
|
const data = await fetchCompanyTeamCashFlow(filter.year.format("YYYY"), filter.teamId) |
|
|
|
return data |
|
|
|
},[needFetch]) |
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
console.log(filter) |
|
|
|
const fetchAndProcess = async () => { |
|
|
|
let income = [...emptyList] |
|
|
|
let cumIncome: number[] = [] |
|
|
|
let cumExpenditure: number[] = [] |
|
|
|
let expenditure = [...emptyList] |
|
|
|
let data: CompanyTeamCashFlow[] = [] |
|
|
|
if (!needFetch) { |
|
|
|
data = cashflow |
|
|
|
} else { |
|
|
|
data = await fetchData(filter) |
|
|
|
} |
|
|
|
for (let i = 0; i < 12; i++ ) { |
|
|
|
const curr = data[i] |
|
|
|
income[i] = curr?.income ?? 0 |
|
|
|
expenditure[i] = curr?.expenditure ?? 0 |
|
|
|
if (curr) { |
|
|
|
cumIncome.push(curr.cumulativeIncome) |
|
|
|
cumExpenditure.push(curr.cumulativeExpenditure) |
|
|
|
} else { |
|
|
|
cumIncome.push(cumIncome[cumIncome.length - 1]) |
|
|
|
cumExpenditure.push(cumExpenditure[cumExpenditure.length - 1]) |
|
|
|
} |
|
|
|
} |
|
|
|
setIncomeList(income) |
|
|
|
setExpenditureList(expenditure) |
|
|
|
setCumulativeIncomeList(cumIncome) |
|
|
|
setCumulativeExpenditureList(cumExpenditure) |
|
|
|
setLeftMax(Math.max(...income,...expenditure)) |
|
|
|
setRightMax(Math.max(...cumIncome,...cumExpenditure)) |
|
|
|
} |
|
|
|
fetchAndProcess() |
|
|
|
}, [filter, needFetch]) |
|
|
|
|
|
|
|
// useEffect(()=>{ |
|
|
|
// console.log(filter) |
|
|
|
// // console.log(expenditureList) |
|
|
|
// },[filter]) |
|
|
|
|
|
|
|
return ( |
|
|
|
<Grid container> |
|
|
|
<Grid item xs={12} md={12} lg={12}> |
|
|
|
<Typography variant="h4" marginInlineEnd={2}> |
|
|
|
{t("Company / Team Cash Flow")} |
|
|
|
</Typography> |
|
|
|
</Grid> |
|
|
|
<Grid item xs={12} md={12} lg={12}> |
|
|
|
<Card> |
|
|
|
<CardHeader |
|
|
|
className="text-slate-500" |
|
|
|
title={t("Company and Team Cash Flow By Month")} |
|
|
|
/> |
|
|
|
<CardContent> |
|
|
|
<Grid container> |
|
|
|
<Grid item xs={12} md={12} lg={12} sx={{mt:-3, display: 'flex', gap: 2}}> |
|
|
|
<LocalizationProvider |
|
|
|
dateAdapter={AdapterDayjs} |
|
|
|
adapterLocale={`${language}-hk`}> |
|
|
|
<DatePicker |
|
|
|
views={['year']} |
|
|
|
label={t("Year")} |
|
|
|
value={filter.year} |
|
|
|
onChange={(newValue) => { |
|
|
|
setNeedFetch(true) |
|
|
|
setFilter((prev) => ({...prev, year: newValue!!})) |
|
|
|
}} |
|
|
|
sx={{ width: 240 }} |
|
|
|
slotProps={{ |
|
|
|
textField: { |
|
|
|
size: 'medium' |
|
|
|
} |
|
|
|
}} |
|
|
|
/> |
|
|
|
</LocalizationProvider> |
|
|
|
{teams.length > 0 && <Autocomplete |
|
|
|
id="test" |
|
|
|
options={[{id: 0, label: "All Teams"}, ...teams.map((t) => ({id: t.id, label: t.name}))]} |
|
|
|
isOptionEqualToValue={(option, value) => option.id === value.id} |
|
|
|
getOptionLabel={(option) => option.label} |
|
|
|
defaultValue={{id: 0, label: "All Teams"}} |
|
|
|
sx={{ width: 240 }} |
|
|
|
onChange={(_, value) => { |
|
|
|
setNeedFetch(true) |
|
|
|
if (value) setFilter((prev) => ({...prev, teamId: value.id})); |
|
|
|
}} |
|
|
|
renderInput={(params) => <TextField {...params}/>} |
|
|
|
/> |
|
|
|
} |
|
|
|
</Grid> |
|
|
|
<Grid item xs={12} md={12} lg={12}> |
|
|
|
<ReactApexChart |
|
|
|
options={options} |
|
|
|
series={options.series} |
|
|
|
type="line" |
|
|
|
height="500" |
|
|
|
/> |
|
|
|
</Grid> |
|
|
|
</Grid> |
|
|
|
</CardContent> |
|
|
|
</Card> |
|
|
|
</Grid> |
|
|
|
</Grid> |
|
|
|
) |
|
|
|
} |
|
|
|
export default CompanyTeamCashFlowV2 |