From a86b72c389cc9294b57c9a282e08f380fc25cd20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Gonz=C3=A1lez?= Date: Mon, 4 Mar 2024 18:33:24 +0100 Subject: [PATCH] eudr - suppliers breakdown foundation --- client/components.json | 17 + client/package.json | 10 +- .../forms/select/autocomplete/component.tsx | 2 +- .../src/components/forms/select/component.tsx | 2 +- client/src/components/table/cell.tsx | 2 +- .../table/pagination/button/component.tsx | 2 +- client/src/components/tabs/component.tsx | 2 +- .../src/components/tree-select/component.tsx | 2 +- client/src/components/ui/collapsible.tsx | 9 + client/src/components/ui/label.tsx | 19 + client/src/components/ui/radio-group.tsx | 36 + .../breakdown/breakdown-item/index.tsx | 32 + .../deforestation-free-suppliers/index.tsx | 16 + .../index.tsx | 5 + .../suppliers-with-no-location-data/index.tsx | 5 + .../analysis-eudr/category-list/index.tsx | 101 +++ .../suppliers-stacked-bar/component.tsx | 139 ++-- .../material-legend-item/component.tsx | 2 +- .../comparison-cell/component.tsx | 2 +- .../containers/mobile-header/component.tsx | 2 +- .../navigation/mobile/component.tsx | 2 +- client/src/lib/utils.ts | 6 + client/src/pages/404.tsx | 2 +- client/src/styles/globals.css | 83 +- client/src/utils/colors.ts | 2 +- client/src/utils/number-format.ts | 4 + client/tailwind.config.ts | 146 ++++ ...lwind.config.js => tailwind.config_old.js} | 0 client/yarn.lock | 763 +++++++++++++++--- 29 files changed, 1234 insertions(+), 181 deletions(-) create mode 100644 client/components.json create mode 100644 client/src/components/ui/collapsible.tsx create mode 100644 client/src/components/ui/label.tsx create mode 100644 client/src/components/ui/radio-group.tsx create mode 100644 client/src/containers/analysis-eudr/category-list/breakdown/breakdown-item/index.tsx create mode 100644 client/src/containers/analysis-eudr/category-list/breakdown/deforestation-free-suppliers/index.tsx create mode 100644 client/src/containers/analysis-eudr/category-list/breakdown/suppliers-with-deforestation-alerts/index.tsx create mode 100644 client/src/containers/analysis-eudr/category-list/breakdown/suppliers-with-no-location-data/index.tsx create mode 100644 client/src/containers/analysis-eudr/category-list/index.tsx create mode 100644 client/src/lib/utils.ts create mode 100644 client/tailwind.config.ts rename client/{tailwind.config.js => tailwind.config_old.js} (100%) diff --git a/client/components.json b/client/components.json new file mode 100644 index 000000000..032a4c479 --- /dev/null +++ b/client/components.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "src/styles/globals.css", + "baseColor": "slate", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils" + } +} diff --git a/client/package.json b/client/package.json index 216213c40..ca08044d5 100644 --- a/client/package.json +++ b/client/package.json @@ -34,6 +34,9 @@ "@json2csv/plainjs": "^6.1.3", "@loaders.gl/core": "3.3.1", "@luma.gl/constants": "8.5.18", + "@radix-ui/react-collapsible": "1.0.3", + "@radix-ui/react-label": "2.0.2", + "@radix-ui/react-radio-group": "1.1.3", "@reduxjs/toolkit": "1.8.2", "@tailwindcss/forms": "0.4.0", "@tailwindcss/typography": "0.5.0", @@ -43,7 +46,9 @@ "autoprefixer": "10.2.5", "axios": "1.3.4", "chroma-js": "2.1.2", + "class-variance-authority": "0.7.0", "classnames": "2.3.1", + "clsx": "^2.1.0", "d3-array": "3.0.2", "d3-format": "3.0.1", "d3-scale": "4.0.2", @@ -52,6 +57,7 @@ "jsona": "1.9.2", "lodash-es": "4.17.21", "lottie-react": "2.4.0", + "lucide-react": "0.344.0", "maplibre-gl": "3.6.2", "next": "13.5.5", "next-auth": "4.19.2", @@ -70,7 +76,9 @@ "recharts": "2.9.0", "rooks": "7.14.1", "sharp": "0.32.6", - "tailwindcss": "3.3.1", + "tailwind-merge": "2.2.1", + "tailwindcss": "3.4.1", + "tailwindcss-animate": "1.0.7", "uuid": "8.3.2", "yup": "0.32.11" }, diff --git a/client/src/components/forms/select/autocomplete/component.tsx b/client/src/components/forms/select/autocomplete/component.tsx index a385b38d2..ac43735aa 100644 --- a/client/src/components/forms/select/autocomplete/component.tsx +++ b/client/src/components/forms/select/autocomplete/component.tsx @@ -124,7 +124,7 @@ const AutoCompleteSelect = ({ {({ open }) => ( <> {!!label && ( - + {label} )} diff --git a/client/src/components/forms/select/component.tsx b/client/src/components/forms/select/component.tsx index ce018f19a..53618df35 100644 --- a/client/src/components/forms/select/component.tsx +++ b/client/src/components/forms/select/component.tsx @@ -133,7 +133,7 @@ const Select = ({ {({ open }) => ( <> {!!label && ( - + {label} )} diff --git a/client/src/components/table/cell.tsx b/client/src/components/table/cell.tsx index 3ed99bab0..b8201825f 100644 --- a/client/src/components/table/cell.tsx +++ b/client/src/components/table/cell.tsx @@ -40,7 +40,7 @@ const CellWrapper = ({ children, context }: React.PropsWithChildren = ({ 'flex h-10 min-w-[2.5rem] items-center justify-center text-sm', className, { - 'border-green-700 border-b': active, + 'border-b border-green-700': active, 'cursor-pointer': !disabled, 'opacity-30': disabled, }, diff --git a/client/src/components/tabs/component.tsx b/client/src/components/tabs/component.tsx index c545f6a46..466eae3fb 100644 --- a/client/src/components/tabs/component.tsx +++ b/client/src/components/tabs/component.tsx @@ -11,7 +11,7 @@ const Tabs: React.FC = ({ activeTab, tabs, bottomBorder = true }: Tab href={tab.href} className={classNames('-mb-px py-3', { 'ml-10': index !== 0, - 'text-green-700 border-green-700 border-b-2': activeTab && tab === activeTab, + 'border-b-2 border-green-700 text-green-700': activeTab && tab === activeTab, })} > {tab.name} diff --git a/client/src/components/tree-select/component.tsx b/client/src/components/tree-select/component.tsx index bce8ce91e..1aea66c27 100644 --- a/client/src/components/tree-select/component.tsx +++ b/client/src/components/tree-select/component.tsx @@ -513,7 +513,7 @@ const InnerTreeSelect = ( {theme === 'inline-primary' ? (
diff --git a/client/src/components/ui/collapsible.tsx b/client/src/components/ui/collapsible.tsx new file mode 100644 index 000000000..9605c4e41 --- /dev/null +++ b/client/src/components/ui/collapsible.tsx @@ -0,0 +1,9 @@ +import * as CollapsiblePrimitive from '@radix-ui/react-collapsible'; + +const Collapsible = CollapsiblePrimitive.Root; + +const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger; + +const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent; + +export { Collapsible, CollapsibleTrigger, CollapsibleContent }; diff --git a/client/src/components/ui/label.tsx b/client/src/components/ui/label.tsx new file mode 100644 index 000000000..470162f1b --- /dev/null +++ b/client/src/components/ui/label.tsx @@ -0,0 +1,19 @@ +import * as React from 'react'; +import * as LabelPrimitive from '@radix-ui/react-label'; +import { cva, type VariantProps } from 'class-variance-authority'; + +import { cn } from '@/lib/utils'; + +const labelVariants = cva( + 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70', +); + +const Label = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & VariantProps +>(({ className, ...props }, ref) => ( + +)); +Label.displayName = LabelPrimitive.Root.displayName; + +export { Label }; diff --git a/client/src/components/ui/radio-group.tsx b/client/src/components/ui/radio-group.tsx new file mode 100644 index 000000000..1cf4b798e --- /dev/null +++ b/client/src/components/ui/radio-group.tsx @@ -0,0 +1,36 @@ +import * as React from 'react'; +import * as RadioGroupPrimitive from '@radix-ui/react-radio-group'; +import { Circle } from 'lucide-react'; + +import { cn } from '@/lib/utils'; + +const RadioGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + return ; +}); +RadioGroup.displayName = RadioGroupPrimitive.Root.displayName; + +const RadioGroupItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + return ( + + + + + + ); +}); +RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName; + +export { RadioGroup, RadioGroupItem }; diff --git a/client/src/containers/analysis-eudr/category-list/breakdown/breakdown-item/index.tsx b/client/src/containers/analysis-eudr/category-list/breakdown/breakdown-item/index.tsx new file mode 100644 index 000000000..c2317c0e9 --- /dev/null +++ b/client/src/containers/analysis-eudr/category-list/breakdown/breakdown-item/index.tsx @@ -0,0 +1,32 @@ +import { formatPercentage } from '@/utils/number-format'; + +const BreakdownItem = ({ + name, + color, + icon, + value, +}: { + name: string; + color: string; + icon: string; + value: number; +}): JSX.Element => { + return ( +
+
+
+ {name} +
+
+
+ {formatPercentage(value)} of suppliers +
+
+
+
+
+
+ ); +}; + +export default BreakdownItem; diff --git a/client/src/containers/analysis-eudr/category-list/breakdown/deforestation-free-suppliers/index.tsx b/client/src/containers/analysis-eudr/category-list/breakdown/deforestation-free-suppliers/index.tsx new file mode 100644 index 000000000..801873779 --- /dev/null +++ b/client/src/containers/analysis-eudr/category-list/breakdown/deforestation-free-suppliers/index.tsx @@ -0,0 +1,16 @@ +const SAMPLE_DATA = { + byMaterial: [ + { name: 'Supplier 1', value: 100 }, + { name: 'Supplier 1', value: 100 }, + ], + byOrigin: [ + { name: 'Supplier 1', value: 100, iso3: 'ITA' }, + { name: 'Supplier 1', value: 100, iso3: 'ITA' }, + ], +}; + +const DeforestationFreeSuppliersBreakdown = () => { + return
DeforestationFreeSuppliersBreakdown
; +}; + +export default DeforestationFreeSuppliersBreakdown; diff --git a/client/src/containers/analysis-eudr/category-list/breakdown/suppliers-with-deforestation-alerts/index.tsx b/client/src/containers/analysis-eudr/category-list/breakdown/suppliers-with-deforestation-alerts/index.tsx new file mode 100644 index 000000000..4a5546533 --- /dev/null +++ b/client/src/containers/analysis-eudr/category-list/breakdown/suppliers-with-deforestation-alerts/index.tsx @@ -0,0 +1,5 @@ +const SuppliersWithDeforestationAlertsBreakdown = () => { + return
SuppliersWithDeforestationAlertsBreakdown
; +}; + +export default SuppliersWithDeforestationAlertsBreakdown; diff --git a/client/src/containers/analysis-eudr/category-list/breakdown/suppliers-with-no-location-data/index.tsx b/client/src/containers/analysis-eudr/category-list/breakdown/suppliers-with-no-location-data/index.tsx new file mode 100644 index 000000000..dbd62935f --- /dev/null +++ b/client/src/containers/analysis-eudr/category-list/breakdown/suppliers-with-no-location-data/index.tsx @@ -0,0 +1,5 @@ +const SuppliersWithNoLocationDataBreakdown = () => { + return
SuppliersWithNoLocationData
; +}; + +export default SuppliersWithNoLocationDataBreakdown; diff --git a/client/src/containers/analysis-eudr/category-list/index.tsx b/client/src/containers/analysis-eudr/category-list/index.tsx new file mode 100644 index 000000000..98744d88b --- /dev/null +++ b/client/src/containers/analysis-eudr/category-list/index.tsx @@ -0,0 +1,101 @@ +import { useState } from 'react'; + +import DeforestationFreeSuppliersBreakdown from './breakdown/deforestation-free-suppliers'; +import SuppliersWithDeforestationAlertsBreakdown from './breakdown/suppliers-with-deforestation-alerts'; +import SuppliersWithNoLocationDataBreakdown from './breakdown/suppliers-with-no-location-data'; + +import { Button } from '@/components/button'; +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; +import { formatPercentage } from '@/utils/number-format'; + +const CATEGORIES = [ + { + name: 'Deforestation-free suppliers', + slug: 'deforestation-free-suppliers', + color: 'bg-[#4AB7F3]', + // todo move this value field to the component + value: 0.3, + }, + { + name: 'Suppliers with deforestation alerts', + slug: 'suppliers-with-deforestation-alerts', + color: 'bg-[#FFC038]', + // todo move this value field to the component + value: 0.6, + }, + { + name: 'Suppliers with no location data', + slug: 'suppliers-with-no-location-data', + color: 'bg-[#8460FF]', + // todo move this value field to the component + value: 0.1, + }, +] as const; + +type CategoryState = Record<(typeof CATEGORIES)[number]['slug'], boolean>; + +export const CategoryList = (): JSX.Element => { + const [categories, toggleCategory] = useState( + CATEGORIES.reduce( + (acc, category) => ({ + ...acc, + [category.slug]: false, + }), + {} as CategoryState, + ), + ); + const categoriesWithValues = CATEGORIES.map((category) => ({ + ...category, + // todo: calculate value field here + })); + + return ( + <> + {categoriesWithValues.map((category) => ( + { + toggleCategory((prev) => ({ + ...prev, + [category.slug]: !prev[category.slug], + })); + }} + > +
+
{category.name}
+
+
+ {formatPercentage(category.value)} of suppliers +
+
+
+
+
+ + + +
+ + {category.slug === 'deforestation-free-suppliers' && ( + + )} + {category.slug === 'suppliers-with-deforestation-alerts' && ( + + )} + {category.slug === 'suppliers-with-no-location-data' && ( + + )} + + + ))} + + ); +}; + +export default CategoryList; diff --git a/client/src/containers/analysis-eudr/suppliers-stacked-bar/component.tsx b/client/src/containers/analysis-eudr/suppliers-stacked-bar/component.tsx index b5cfc0a7b..6207e4cd5 100644 --- a/client/src/containers/analysis-eudr/suppliers-stacked-bar/component.tsx +++ b/client/src/containers/analysis-eudr/suppliers-stacked-bar/component.tsx @@ -10,7 +10,23 @@ import { Label, } from 'recharts'; -import { Button } from '@/components/button'; +import CategoryList from '@/containers/analysis-eudr/category-list'; +import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; +import { Label as RadioLabel } from '@/components/ui/label'; + +const VIEW_BY_OPTIONS = [ + { + label: 'Commodities', + value: 'commodities', + defaultChecked: true, + }, + { + label: 'Countries', + value: 'countries', + }, +]; + +const defaultViewBy = VIEW_BY_OPTIONS.find((option) => option.defaultChecked)?.value; const data = [ { @@ -66,8 +82,16 @@ const SuppliersStackedBar = () => {

Suppliers by category

-
+
View by:
+ + {VIEW_BY_OPTIONS.map((option) => ( +
+ + {option.label} +
+ ))} +
@@ -121,7 +145,7 @@ const SuppliersStackedBar = () => { type="category" width={200} /> - + @@ -129,54 +153,73 @@ const SuppliersStackedBar = () => {
-
-
Deforestation-free suppliers
-
-
- 31% of suppliers -
-
-
-
-
-
- -
-
-
-
Suppliers with deforestation alerts
-
-
- 36% of suppliers -
-
-
+ + {/* + +
+
Deforestation-free suppliers
+
+
+ 31% of suppliers +
+
+
+
+
+ +
-
-
- -
-
-
-
Suppliers with no location data
-
-
- 33% of suppliers + + +
test
+
+ + + +
+
Suppliers with deforestation alerts
+
+
+ 36% of suppliers +
+
+
+
+
+
-
-
+ + +
test
+
+ + + +
+
Suppliers with no location data
+
+
+ 33% of suppliers +
+
+
+
+
+
+ +
-
-
- -
-
+ + +
test
+
+ */}
); diff --git a/client/src/containers/analysis-visualization/analysis-legend/material-legend-item/component.tsx b/client/src/containers/analysis-visualization/analysis-legend/material-legend-item/component.tsx index a9183402a..c1ef5cf8c 100644 --- a/client/src/containers/analysis-visualization/analysis-legend/material-legend-item/component.tsx +++ b/client/src/containers/analysis-visualization/analysis-legend/material-legend-item/component.tsx @@ -127,7 +127,7 @@ const MaterialLayer = () => { /> )} {isError && error.response?.status === 404 && ( -
No data found for this parameters
+
No data found for this parameters
)} ); diff --git a/client/src/containers/analysis-visualization/analysis-table/comparison-cell/component.tsx b/client/src/containers/analysis-visualization/analysis-table/comparison-cell/component.tsx index 2c11ed742..20281e8ee 100644 --- a/client/src/containers/analysis-visualization/analysis-table/comparison-cell/component.tsx +++ b/client/src/containers/analysis-visualization/analysis-table/comparison-cell/component.tsx @@ -49,7 +49,7 @@ const ComparisonCell: React.FC = ({ className={classNames( 'my-auto rounded-[4px] px-1 py-0.5 text-xs font-semibold text-gray-500', { - 'text-green-700 bg-green-400/40': + 'bg-green-400/40 text-green-700': (comparisonMode === 'relative' && percentageDifference <= 0) || (comparisonMode === 'absolute' && absoluteDifference <= 0), 'bg-red-400/20 text-red-800': diff --git a/client/src/containers/mobile-header/component.tsx b/client/src/containers/mobile-header/component.tsx index 4f94411e5..9c80f0904 100644 --- a/client/src/containers/mobile-header/component.tsx +++ b/client/src/containers/mobile-header/component.tsx @@ -9,7 +9,7 @@ const HeaderMobile = () => { return (
-
+
diff --git a/client/src/containers/navigation/mobile/component.tsx b/client/src/containers/navigation/mobile/component.tsx index c5e3e119a..f8eceabd2 100644 --- a/client/src/containers/navigation/mobile/component.tsx +++ b/client/src/containers/navigation/mobile/component.tsx @@ -7,7 +7,7 @@ const MobileNavigation = ({ items }: NavigationProps) => (