Sfoglia il codice sorgente

update

tags/Baseline_180220205_Frontend
MSI\derek 9 mesi fa
parent
commit
4bb13e0693
4 ha cambiato i file con 323 aggiunte e 6 eliminazioni
  1. +17
    -0
      src/app/api/teamCashflow/index.ts
  2. +283
    -0
      src/components/CompanyTeamCashFlow/CompanyTeamCashFlowV2.tsx
  3. +20
    -2
      src/components/CompanyTeamCashFlow/CompanyTeamCashFlowWrapper.tsx
  4. +3
    -4
      src/components/ProjectFinancialSummaryV2/gptFn.tsx

+ 17
- 0
src/app/api/teamCashflow/index.ts Vedi File

@@ -23,6 +23,14 @@ export interface teamCashFlow {
cumulativeExpenditure: number;
}

export type CompanyTeamCashFlow = {
month: number,
expenditure: number,
income: number,
cumulativeExpenditure: number,
cumulativeIncome: number,
}

export const fetchTeamCombo = cache(async () => {
return serverFetchJson<teamCombo>(`${BASE_API_URL}/team/combo`);
});
@@ -34,3 +42,12 @@ export const fetchTeamCashFlowChartData = cache(async (year:number,teamId?:numbe
return serverFetchJson<teamCashFlow>(`${BASE_API_URL}/dashboard/searchTeamCashFlow?teamId=${teamId}&year=${year}`);
}
});

export const fetchCompanyTeamCashFlow = cache(async (year: string, teamId: number | null,) => {
var endpoint = `${BASE_API_URL}/dashboard/getCompanyTeamCashFlow?year=${year}`
if (teamId) endpoint += `&teamId=${teamId}`
return serverFetchJson<CompanyTeamCashFlow[]>(endpoint, {
next: { tags: [year] },
});
});


+ 283
- 0
src/components/CompanyTeamCashFlow/CompanyTeamCashFlowV2.tsx Vedi File

@@ -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

+ 20
- 2
src/components/CompanyTeamCashFlow/CompanyTeamCashFlowWrapper.tsx Vedi File

@@ -1,11 +1,29 @@
import React from "react";
import CompanyTeamCashFlow from "./CompanyTeamCashFlow";
import { fetchUserAbilities, fetchUserStaff } from "@/app/utils/fetchUtil";
import { VIEW_DASHBOARD_ALL } from "@/middleware";
import CompanyTeamCashFlow from "./CompanyTeamCashFlow";
import dayjs from "dayjs";
import { fetchCompanyTeamCashFlow } from "@/app/api/teamCashflow";
import CompanyTeamCashFlowV2 from "./CompanyTeamCashFlowV2";
import { fetchTeam } from "@/app/api/team";

const CompanyTeamCashFlowWrapper: React.FC = async () => {
const [abilities, staff] = await Promise.all([fetchUserAbilities(), fetchUserStaff()]);

const currYear = dayjs().format("YYYY")
const teamId = abilities.includes(VIEW_DASHBOARD_ALL) ? null : staff.teamId
const cashflow = await fetchCompanyTeamCashFlow(currYear, teamId)
const viewAll = abilities.includes(VIEW_DASHBOARD_ALL)
// own team / all teams depends on abilities
return <CompanyTeamCashFlow abilities={abilities} staff={staff}/>;
// return <CompanyTeamCashFlowV2 cashflow={cashflow} teams={viewAll ? await fetchTeam() : []}/>
// return <CompanyTeamCashFlow2
// viewAll={viewAll}
// _teamId={teamId}
// _manpowerExpense={manhourExpense}
// _invoice={invoice}
// _projectExpense={projectExpense}
// // cashflow={cashflow}
// />
};

export default CompanyTeamCashFlowWrapper;

+ 3
- 4
src/components/ProjectFinancialSummaryV2/gptFn.tsx Vedi File

@@ -1,4 +1,4 @@
import { FinancialSummaryByProject, FinancialSummaryByClient, FinancialSummaryType, FinancialByProject } from "@/app/api/financialsummary";
import { FinancialByProject } from "@/app/api/financialsummary";

export type SumOfByTeam = {
id: number,
@@ -29,8 +29,8 @@ export function sumUpByClient(data: FinancialByProject[]): SumOfByClient[] {
if (!acc[item.custId]) {
acc[item.custId] = {
id: item.custId,
customerCode: item.customerName,
customerName: item.customerCode,
customerCode: item.customerCode,
customerName: item.customerName,
totalFee: 0,
totalBudget: 0,
manhourExpense: 0,
@@ -81,6 +81,5 @@ export function sumUpByTeam(data: FinancialByProject[]): SumOfByTeam[] {
return acc;
}, {});

// Convert the result object to an array
return Object.values(result);
}

Caricamento…
Annulla
Salva