From f12bb1fb9c2f6019e94c90be7a1b49340a1021f7 Mon Sep 17 00:00:00 2001 From: atrincas Date: Mon, 2 Dec 2024 13:37:40 +0100 Subject: [PATCH] Initial setup AnnualProjectCashFlow --- client/package.json | 1 + client/src/app/globals.css | 39 +- client/src/app/projects/[id]/store.ts | 8 +- client/src/components/ui/chart.tsx | 368 ++++++++ client/src/components/ui/metric.tsx | 2 +- .../annual-project-cash-flow/chart/index.tsx | 161 ++++ .../header/tabs/index.tsx | 8 +- .../annual-project-cash-flow/index.tsx | 9 +- .../table/columns.tsx | 56 ++ .../annual-project-cash-flow/table/index.tsx | 124 +++ .../custom-project/cost-details/index.tsx | 11 +- .../cost-details/parameters/index.tsx | 30 +- .../cost-details/table/columns.tsx | 12 +- .../cost-details/table/index.tsx | 40 +- .../projects/custom-project/cost/index.tsx | 17 +- .../projects/custom-project/details/index.tsx | 10 +- .../projects/custom-project/index.tsx | 45 +- .../custom-project/left-over/index.tsx | 18 +- .../projects/custom-project/mock-data.ts | 819 ++++++++++++++---- .../projects/custom-project/summary/index.tsx | 47 +- pnpm-lock.yaml | 80 ++ 21 files changed, 1611 insertions(+), 294 deletions(-) create mode 100644 client/src/components/ui/chart.tsx create mode 100644 client/src/containers/projects/custom-project/annual-project-cash-flow/chart/index.tsx create mode 100644 client/src/containers/projects/custom-project/annual-project-cash-flow/table/columns.tsx create mode 100644 client/src/containers/projects/custom-project/annual-project-cash-flow/table/index.tsx diff --git a/client/package.json b/client/package.json index af4d39cf..377dd005 100644 --- a/client/package.json +++ b/client/package.json @@ -49,6 +49,7 @@ "react-dropzone": "^14.3.5", "react-map-gl": "7.1.7", "react-resizable-panels": "2.1.6", + "recharts": "^2.13.3", "rooks": "7.14.1", "tailwind-merge": "2.5.3", "tailwindcss-animate": "1.0.7", diff --git a/client/src/app/globals.css b/client/src/app/globals.css index bae7e2b4..49ff070b 100644 --- a/client/src/app/globals.css +++ b/client/src/app/globals.css @@ -4,7 +4,6 @@ @tailwind components; @tailwind utilities; - body { font-family: Arial, Helvetica, sans-serif; } @@ -50,6 +49,11 @@ body { --sidebar-accent-foreground: 192, 86%, 69%; --sidebar-border: 220 13% 91%; --sidebar-ring: 212, 57%, 24%; + --chart-1: 150 43% 61%; + --chart-2: 194 88% 43%; + --chart-3: 191 87% 82%; + --chart-4: 50 80% 58%; + --chart-5: 213 73% 97%; } } @@ -65,31 +69,52 @@ body { /* mapbox styles */ .mapboxgl-popup-anchor-top { .mapboxgl-popup-tip { - @apply !border-b-popover !z-10 relative; + @apply relative !z-10 !border-b-popover; } } .mapboxgl-popup-anchor-bottom { .mapboxgl-popup-tip { - @apply !border-t-popover !z-10 relative; + @apply relative !z-10 !border-t-popover; } } .mapboxgl-popup-anchor-left { .mapboxgl-popup-tip { - @apply !border-r-popover !z-10 relative; + @apply relative !z-10 !border-r-popover; } } .mapboxgl-popup-anchor-right { .mapboxgl-popup-tip { - @apply !border-l-popover !z-10 relative; + @apply relative !z-10 !border-l-popover; } } .mapboxgl-popup-content { - @apply !bg-popover !rounded-md border border-border text-big-stone-50 shadow-md; + @apply !rounded-md border border-border !bg-popover text-big-stone-50 shadow-md; } .mapboxgl-popup-close-button { @apply hidden; -} \ No newline at end of file +} + +/* cashflow chart */ +.cashflow-chart { + .recharts-cartesian-grid-vertical line { + stroke-dasharray: 3, 3; + + &:nth-last-child(1), + &:nth-last-child(2) { + stroke: transparent; + } + } + + .recharts-cartesian-grid-horizontal line { + stroke-dasharray: 0; + + &:first-of-type, + &:last-of-type { + stroke: transparent; + } + } +} diff --git a/client/src/app/projects/[id]/store.ts b/client/src/app/projects/[id]/store.ts index 9eb5bf4e..c174aea4 100644 --- a/client/src/app/projects/[id]/store.ts +++ b/client/src/app/projects/[id]/store.ts @@ -1,3 +1,4 @@ +import { COST_TYPE_SELECTOR } from "@shared/entities/projects.entity"; import { atom } from "jotai"; import { parseAsStringLiteral, useQueryState } from "nuqs"; @@ -9,10 +10,13 @@ export const projectsUIState = atom<{ projectSummaryOpen: false, }); export const showCostDetailsAtom = atom(false); +export const costDetailsFilterAtom = atom( + COST_TYPE_SELECTOR.TOTAL, +); -export function useProjectCashFlowView() { +export function useProjectCashFlowTab() { return useQueryState( - "cashflow", + "cashflowTab", parseAsStringLiteral(CASH_FLOW_VIEWS).withDefault("chart"), ); } diff --git a/client/src/components/ui/chart.tsx b/client/src/components/ui/chart.tsx new file mode 100644 index 00000000..f0f6dd73 --- /dev/null +++ b/client/src/components/ui/chart.tsx @@ -0,0 +1,368 @@ +"use client"; + +import * as React from "react"; + +import * as RechartsPrimitive from "recharts"; + +import { cn } from "@/lib/utils"; + +// Format: { THEME_NAME: CSS_SELECTOR } +const THEMES = { light: "", dark: ".dark" } as const; + +export type ChartConfig = { + [k in string]: { + label?: React.ReactNode; + icon?: React.ComponentType; + } & ( + | { color?: string; theme?: never } + | { color?: never; theme: Record } + ); +}; + +type ChartContextProps = { + config: ChartConfig; +}; + +const ChartContext = React.createContext(null); + +function useChart() { + const context = React.useContext(ChartContext); + + if (!context) { + throw new Error("useChart must be used within a "); + } + + return context; +} + +const ChartContainer = React.forwardRef< + HTMLDivElement, + React.ComponentProps<"div"> & { + config: ChartConfig; + children: React.ComponentProps< + typeof RechartsPrimitive.ResponsiveContainer + >["children"]; + } +>(({ id, className, children, config, ...props }, ref) => { + const uniqueId = React.useId(); + const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`; + + return ( + +
+ + + {children} + +
+
+ ); +}); +ChartContainer.displayName = "Chart"; + +const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => { + const colorConfig = Object.entries(config).filter( + ([, config]) => config.theme || config.color, + ); + + if (!colorConfig.length) { + return null; + } + + return ( +