diff --git a/package-lock.json b/package-lock.json index 7aa87d0b..670f2749 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,20 +12,31 @@ "@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-dialog": "^1.0.5", + "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-label": "^2.0.2", + "@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-radio-group": "^1.1.3", "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-slot": "^1.0.2", + "@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-toast": "^1.1.5", + "@radix-ui/react-toggle": "^1.0.3", + "@radix-ui/react-toggle-group": "^1.0.4", "@radix-ui/react-tooltip": "^1.0.7", + "@tanstack/react-table": "^8.17.3", "axios": "^1.6.8", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", +<<<<<<< feat/add-editable-data-table-to-manage-shelter-supplies + "cmdk": "^1.0.0", +======= "crypto-js": "^4.2.0", +>>>>>>> develop "date-fns": "^3.6.0", "formik": "^2.4.6", "lucide-react": "^0.378.0", + "next-themes": "^0.3.0", "qs": "^6.12.1", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -33,6 +44,7 @@ "react-input-mask": "^2.0.4", "react-router-dom": "^6.23.0", "react-select": "^5.8.0", + "sonner": "^1.4.41", "tailwind-merge": "^2.3.0", "tailwindcss-animate": "^1.0.7", "yup": "^1.4.0" @@ -1605,6 +1617,35 @@ } } }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.0.6.tgz", + "integrity": "sha512-i6TuFOoWmLWq+M/eCLGd/bQ2HfAX1RJgvrBQ6AQLmzfvsLdefxbWu8G9zczcPFfcSPehz9GcpF6K9QYreFV8hA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-menu": "2.0.6", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-controllable-state": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-focus-guards": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz", @@ -1688,6 +1729,83 @@ } } }, + "node_modules/@radix-ui/react-menu": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.0.6.tgz", + "integrity": "sha512-BVkFLS+bUC8HcImkRKPSiVumA1VPOOEC5WBMiT+QAVsPzW1FJzI9KnqgGxVDPBcql5xXrHkD3JOVoXWEXD8SYA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-collection": "1.0.3", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-direction": "1.0.1", + "@radix-ui/react-dismissable-layer": "1.0.5", + "@radix-ui/react-focus-guards": "1.0.1", + "@radix-ui/react-focus-scope": "1.0.4", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-popper": "1.1.3", + "@radix-ui/react-portal": "1.0.4", + "@radix-ui/react-presence": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-roving-focus": "1.0.4", + "@radix-ui/react-slot": "1.0.2", + "@radix-ui/react-use-callback-ref": "1.0.1", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.5" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.0.7.tgz", + "integrity": "sha512-shtvVnlsxT6faMnK/a7n0wptwBD23xc1Z5mdrtKLwVEfsEMXodS0r5s0/g5P0hX//EKYZS2sxUjqfzlg52ZSnQ==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-dismissable-layer": "1.0.5", + "@radix-ui/react-focus-guards": "1.0.1", + "@radix-ui/react-focus-scope": "1.0.4", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-popper": "1.1.3", + "@radix-ui/react-portal": "1.0.4", + "@radix-ui/react-presence": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-slot": "1.0.2", + "@radix-ui/react-use-controllable-state": "1.0.1", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.5" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popper": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.3.tgz", @@ -1937,6 +2055,35 @@ } } }, + "node_modules/@radix-ui/react-switch": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.0.3.tgz", + "integrity": "sha512-mxm87F88HyHztsI7N+ZUmEoARGkC22YVW5CaC+Byc+HRpuvCrOBPTAnXgf+tZ/7i0Sg/eOePGdMhUKhPaQEqow==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-controllable-state": "1.0.1", + "@radix-ui/react-use-previous": "1.0.1", + "@radix-ui/react-use-size": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-toast": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.1.5.tgz", @@ -1971,6 +2118,60 @@ } } }, + "node_modules/@radix-ui/react-toggle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.0.3.tgz", + "integrity": "sha512-Pkqg3+Bc98ftZGsl60CLANXQBBQ4W3mTFS9EJvNxKMZ7magklKV69/id1mlAlOFDDfHvlCms0fx8fA4CMKDJHg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-controllable-state": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle-group": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.0.4.tgz", + "integrity": "sha512-Uaj/M/cMyiyT9Bx6fOZO0SAG4Cls0GptBWiBmBxofmDbNVnYYoyRWj/2M/6VCi/7qcXFWnHhRUfdfZFvvkuu8A==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-direction": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-roving-focus": "1.0.4", + "@radix-ui/react-toggle": "1.0.3", + "@radix-ui/react-use-controllable-state": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-tooltip": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.0.7.tgz", @@ -2375,6 +2576,37 @@ "win32" ] }, + "node_modules/@tanstack/react-table": { + "version": "8.17.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.17.3.tgz", + "integrity": "sha512-5gwg5SvPD3lNAXPuJJz1fOCEZYk9/GeBFH3w/hCgnfyszOIzwkwgp5I7Q4MJtn0WECp84b5STQUDdmvGi8m3nA==", + "dependencies": { + "@tanstack/table-core": "8.17.3" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.17.3", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.17.3.tgz", + "integrity": "sha512-mPBodDGVL+fl6d90wUREepHa/7lhsghg2A3vFpakEhrhtbIlgNAZiMr7ccTgak5qbHqF14Fwy+W1yFWQt+WmYQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -3187,6 +3419,19 @@ "node": ">=6" } }, + "node_modules/cmdk": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.0.0.tgz", + "integrity": "sha512-gDzVf0a09TvoJ5jnuPvygTB77+XdOSwEmJ88L6XPFPlv7T3RxbP9jgenfylrAMD0+Le1aO0nVjQUzl2g+vjz5Q==", + "dependencies": { + "@radix-ui/react-dialog": "1.0.5", + "@radix-ui/react-primitive": "1.0.3" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -5032,6 +5277,15 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/next-themes": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.3.0.tgz", + "integrity": "sha512-/QHIrsYpd6Kfk7xakK4svpDI5mmXP0gfvCoJdGpZQ2TOrQZmsW0QxjaiLn8wbIKjtm4BTSqLoix4lxYYOnLJ/w==", + "peerDependencies": { + "react": "^16.8 || ^17 || ^18", + "react-dom": "^16.8 || ^17 || ^18" + } + }, "node_modules/node-releases": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", @@ -5950,6 +6204,15 @@ "node": ">=8" } }, + "node_modules/sonner": { + "version": "1.4.41", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-1.4.41.tgz", + "integrity": "sha512-uG511ggnnsw6gcn/X+YKkWPo5ep9il9wYi3QJxHsYe7yTZ4+cOd1wuodOUmOpFuXL+/RE3R04LczdNCDygTDgQ==", + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", diff --git a/package.json b/package.json index 62a1de6d..ece0a428 100644 --- a/package.json +++ b/package.json @@ -14,20 +14,31 @@ "@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-dialog": "^1.0.5", + "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-label": "^2.0.2", + "@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-radio-group": "^1.1.3", "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-slot": "^1.0.2", + "@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-toast": "^1.1.5", + "@radix-ui/react-toggle": "^1.0.3", + "@radix-ui/react-toggle-group": "^1.0.4", "@radix-ui/react-tooltip": "^1.0.7", + "@tanstack/react-table": "^8.17.3", "axios": "^1.6.8", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", +<<<<<<< feat/add-editable-data-table-to-manage-shelter-supplies + "cmdk": "^1.0.0", +======= "crypto-js": "^4.2.0", +>>>>>>> develop "date-fns": "^3.6.0", "formik": "^2.4.6", "lucide-react": "^0.378.0", + "next-themes": "^0.3.0", "qs": "^6.12.1", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -35,6 +46,10 @@ "react-input-mask": "^2.0.4", "react-router-dom": "^6.23.0", "react-select": "^5.8.0", +<<<<<<< feat/add-editable-data-table-to-manage-shelter-supplies + "sonner": "^1.4.41", +======= +>>>>>>> develop "tailwind-merge": "^2.3.0", "tailwindcss-animate": "^1.0.7", "yup": "^1.4.0" @@ -53,7 +68,10 @@ "eslint": "^8.57.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.6", +<<<<<<< feat/add-editable-data-table-to-manage-shelter-supplies +======= "javascript-obfuscator": "^4.1.0", +>>>>>>> develop "postcss": "^8.4.38", "tailwindcss": "^3.4.3", "typescript": "^5.2.2", diff --git a/src/components/CardAboutShelter/CardAboutShelter.tsx b/src/components/CardAboutShelter/CardAboutShelter.tsx index 34525112..781a1168 100644 --- a/src/components/CardAboutShelter/CardAboutShelter.tsx +++ b/src/components/CardAboutShelter/CardAboutShelter.tsx @@ -1,12 +1,9 @@ import { Home, UsersRound, - HandHeart, PawPrint, - Landmark, - Smartphone, - Building, - MapPinned, + DollarSign, + Phone, } from 'lucide-react'; import { Card } from '../ui/card'; @@ -22,21 +19,33 @@ const CardAboutShelter = (props: ICardAboutShelter) => { const check = (v?: string | number | boolean | null) => { return v !== undefined && v !== null; }; - const formatAddress = checkAndFormatAddress(shelter, false); + + const formatAddress = checkAndFormatAddress(shelter, true); return ( - -
Sobre o abrigo
+ +
Sobre
} label={formatAddress} /> - {Boolean(shelter.city) && ( - } label="Cidade:" value={shelter.city} /> - )} - {Boolean(shelter.zipCode) && ( - } label="CEP:" value={shelter.zipCode} /> - )} + } + label="Contato:" + value={ + check(shelter.contact) ? `${shelter.contact}` : 'Não informado' + } + clipboardButton={check(shelter.contact)} + /> {shelter.category === ShelterCategory.Shelter && ( + } + label={`Acolhendo`} + value={ + check(shelter.capacity) + ? `${shelter.shelteredPeople} de ${shelter.capacity} vagas` + : 'Não informado' + } + /> } label={ @@ -55,37 +64,11 @@ const CardAboutShelter = (props: ICardAboutShelter) => { ) } /> - } - label="Pessoas abrigadas:" - value={ - check(shelter.shelteredPeople) - ? `${shelter.shelteredPeople} pessoas` - : 'Não informado' - } - /> - } - label="Capacidade do abrigo:" - value={ - check(shelter.capacity) - ? `${shelter.capacity} pessoas` - : 'Não informado' - } - /> )} } - label="Contato:" - value={ - check(shelter.contact) ? `${shelter.contact}` : 'Não informado' - } - clipboardButton={check(shelter.contact)} - /> - } - label="Chave Pix:" + icon={} + label="Pix:" value={check(shelter.pix) ? `${shelter.pix}` : 'Não informado'} clipboardButton={check(shelter.pix)} /> diff --git a/src/components/CardAboutShelter/components/InfoRow/InfoRow.tsx b/src/components/CardAboutShelter/components/InfoRow/InfoRow.tsx index 9b70fb2a..77051e16 100644 --- a/src/components/CardAboutShelter/components/InfoRow/InfoRow.tsx +++ b/src/components/CardAboutShelter/components/InfoRow/InfoRow.tsx @@ -37,7 +37,7 @@ const InfoRow = React.forwardRef( {...rest} > {React.cloneElement(icon as any, { - className: 'min-w-5 min-h-5 w-5 h-5 stroke-muted-foreground', + className: 'min-w-5 min-h-5 w-5 h-5', })}
diff --git a/src/components/CardAboutShelter/utils.ts b/src/components/CardAboutShelter/utils.ts index 58d00b67..89f1bd9f 100644 --- a/src/components/CardAboutShelter/utils.ts +++ b/src/components/CardAboutShelter/utils.ts @@ -5,23 +5,22 @@ const formatShelterAddressFields = ( Pick > ): string => { - const { street, streetNumber, neighbourhood } = payload; - return [street, streetNumber, neighbourhood].filter(Boolean).join(', '); + const { street, streetNumber, neighbourhood} = payload; + return [street, streetNumber].filter(Boolean).join(', ').concat(' - ',[neighbourhood].filter(Boolean).join('-')); }; const checkAndFormatAddress = ( payload: Partial< Pick< IUseShelterData, - 'address' | 'city' | 'street' | 'streetNumber' | 'neighbourhood' + 'address' | 'city' | 'street' | 'streetNumber' | 'neighbourhood' | 'zipCode' > >, showCity = true ): string => { - const { address, city, ...rest } = payload; + const { city, zipCode, ...rest } = payload; return ( - address ?? - `${formatShelterAddressFields(rest)}${showCity ? ` - ${city}` : ''}` + `${formatShelterAddressFields(rest)}${showCity ? `, ${city} - RS` : ''}${zipCode ? `, ${zipCode}`: ''}` ); }; diff --git a/src/components/ShelterSupplyEditableDataTable/components/columns.tsx b/src/components/ShelterSupplyEditableDataTable/components/columns.tsx new file mode 100644 index 00000000..4d818784 --- /dev/null +++ b/src/components/ShelterSupplyEditableDataTable/components/columns.tsx @@ -0,0 +1,119 @@ +"use client" + +import { ColumnDef } from "@tanstack/react-table" +import { Badge } from "@/components/ui/badge" +import { DataTableColumnHeader } from "./data-table-column-header" +import { IUseShelterDataSupply } from "@/hooks/useShelter/types" +import { PriorityCell } from "./priority-cell" +// import { DataTableRowActions } from "./data-table-row-actions" +import { QuantityCell } from "./quantity-cell" +import { DataTableRowAction } from "./data-table-row-actions" +// import { Checkbox } from "@/components/ui/checkbox" + + +export const columns: ColumnDef[] = [ + // { + // id: "select", + // header: ({ table }) => ( + // table.toggleAllPageRowsSelected(!!value)} + // aria-label="Select all" + // className="translate-y-[2px] + // data-[state=checked]:bg-blue-500 + // " + // /> + // ), + // cell: ({ row }) => ( + // row.toggleSelected(!!value)} + // aria-label="Select row" + // className="translate-y-[2px] + // data-[state=checked]:bg-blue-500 + // " + // /> + // ), + // enableSorting: false, + // enableHiding: false, + // }, + { + accessorFn: (row) => row.supply.name, + id: "supplyName", + header: ({ column }) => ( + + ), + cell: ({ row }) => ( +
+

+ {row.original.supply.name} +

+ + {row.original.supply.supplyCategory.name} + +
+ ), + filterFn: (row, id, value) => { + const searchValue = value.toLowerCase(); + const rowValue = (row.getValue(id) as string).toLowerCase(); + return rowValue.includes(searchValue); + }, + }, + { + accessorFn: (row) => row.supply.supplyCategory.name, + id: "supplyCategoryName", + header: ({ column }) => ( + + ), + cell: ({ row }) => ( +
+ + {row.original.supply.supplyCategory.name} + +
+ ), + filterFn: (row, id, value) => { + // const searchValue = value + // const rowValue = (row.getValue(id) as string) + // return rowValue.includes(searchValue); + return value.includes(row.getValue(id)) + }, + enableGrouping: true, + }, + { + accessorKey: "priority", + header: ({ column }) => ( + + ), + cell: PriorityCell, + // cell: ({ getValue, row, column, table }) => { + // const priority: number = row.getValue("priority") + // return + // }, + filterFn: (row, id, value) => { + return value.includes(row.getValue(id)) + }, + }, + { + accessorKey: "quantity", + header: ({ column }) => ( + + ), + cell: QuantityCell, + filterFn: (row, id, value) => { + return value.includes(row.getValue(id)) + }, + }, + { + id: "actions", + cell: DataTableRowAction + // cell: ({ row }) => , + }, +] diff --git a/src/components/ShelterSupplyEditableDataTable/components/data-table-column-header.tsx b/src/components/ShelterSupplyEditableDataTable/components/data-table-column-header.tsx new file mode 100644 index 00000000..905b92c0 --- /dev/null +++ b/src/components/ShelterSupplyEditableDataTable/components/data-table-column-header.tsx @@ -0,0 +1,61 @@ +import { Column } from "@tanstack/react-table" +import { cn } from "@/lib/utils" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { Button } from "@/components/ui/button" +import { ArrowDownUpIcon, MoveDown, MoveUp } from "lucide-react" + +interface DataTableColumnHeaderProps + extends React.HTMLAttributes { + column: Column + title: string +} + +export function DataTableColumnHeader({ + column, + title, + className, +}: DataTableColumnHeaderProps) { + if (!column.getCanSort()) { + return
{title}
+ } + + return ( +
+ + + + + + column.toggleSorting(false)}> + + Asc + + column.toggleSorting(true)}> + + Desc + + + + +
+ ) +} \ No newline at end of file diff --git a/src/components/ShelterSupplyEditableDataTable/components/data-table-faceted-filter.tsx b/src/components/ShelterSupplyEditableDataTable/components/data-table-faceted-filter.tsx new file mode 100644 index 00000000..a6cf3f92 --- /dev/null +++ b/src/components/ShelterSupplyEditableDataTable/components/data-table-faceted-filter.tsx @@ -0,0 +1,161 @@ +import * as React from "react" +import { Column } from "@tanstack/react-table" +import { cn } from "@/lib/utils" +import { Badge } from "@/components/ui/badge" +import { Button } from "@/components/ui/button" +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, +} from "@/components/ui/command" +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover" +import { Separator } from "@/components/ui/separator" +import { Check, CirclePlus } from "lucide-react" +import { getColorClass } from "./utils" + + +interface DataTableFacetedFilterProps { + column?: Column + title?: string + options: { + label: string + value: string | number + icon?: React.ComponentType<{ className?: string }> + }[] +} + +export function DataTableFacetedFilter({ + column, + title, + options, +}: DataTableFacetedFilterProps) { + const facets = column?.getFacetedUniqueValues() + const selectedValues = new Set(column?.getFilterValue() as Array) + + // Sort options based on the facet values in descending order + const sortedOptions = React.useMemo(() => { + return options.sort((a, b) => { + const aValue = facets?.get(a.value) ?? 0; + const bValue = facets?.get(b.value) ?? 0; + // Reverse the comparison to sort in descending order + return bValue - aValue; + }); + }, [options, facets]); + + return ( + + + + + + + + + Nenhum resultado encontrado. + + {/* {options.map((option) => { */} + {sortedOptions.map((option) => { + const isSelected = selectedValues.has(option.value) + const colorPriorityClass = title === "Prioridade" ? getColorClass(option.value) : ""; + return ( + { + if (isSelected) { + selectedValues.delete(option.value) + } else { + selectedValues.add(option.value) + } + const filterValues = Array.from(selectedValues) + column?.setFilterValue( + filterValues.length ? filterValues : undefined + ) + }} + > +
+ +
+ {option.icon && ( + + )} + {option.label} + {facets?.get(option.value) && ( + + {facets.get(option.value)} + + )} +
+ ) + })} +
+ {selectedValues.size > 0 && ( + <> + + + column?.setFilterValue(undefined)} + className="justify-center text-center" + > + Limpar filtros + + + + )} +
+
+
+
+ ) +} diff --git a/src/components/ShelterSupplyEditableDataTable/components/data-table-pagination.tsx b/src/components/ShelterSupplyEditableDataTable/components/data-table-pagination.tsx new file mode 100644 index 00000000..afe24bea --- /dev/null +++ b/src/components/ShelterSupplyEditableDataTable/components/data-table-pagination.tsx @@ -0,0 +1,91 @@ +import { Table } from "@tanstack/react-table" +import { Button } from "@/components/ui/button" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from "lucide-react" + +interface DataTablePaginationProps { + table: Table +} + +export function DataTablePagination({ + table, +}: DataTablePaginationProps) { + return ( +
+ {/*
+ {table.getFilteredSelectedRowModel().rows.length} de{" "} + {table.getFilteredRowModel().rows.length} suprimento(s) selecionados. +
*/} +
+
+

Itens por página

+ +
+
+ Página {table.getState().pagination.pageIndex + 1} de{" "} + {table.getPageCount()} +
+
+ + + + +
+
+
+ ) +} \ No newline at end of file diff --git a/src/components/ShelterSupplyEditableDataTable/components/data-table-row-actions.tsx b/src/components/ShelterSupplyEditableDataTable/components/data-table-row-actions.tsx new file mode 100644 index 00000000..657271a9 --- /dev/null +++ b/src/components/ShelterSupplyEditableDataTable/components/data-table-row-actions.tsx @@ -0,0 +1,74 @@ +"use client" +import { Button } from "@/components/ui/button" +import { useToast } from "@/components/ui/use-toast"; +import { cn } from "@/lib/utils"; +// import { Row } from "@tanstack/react-table" +import { Trash2, Undo } from "lucide-react" + +// interface DataTableRowActionsProps { +// row: Row +// } + +// export function DataTableRowActions({ +// row, +// }: DataTableRowActionsProps) { + +export function DataTableRowAction( + { + // getValue, + // column, + row, + table + // TODO: change any + }: any) { + const { toast } = useToast(); + + const supplyId = row.original.supply.id as string + + const { newData } = table.options.meta + const rowIsModified = newData && newData.some((item: any) => item.supply.id === supplyId); + + function handleResetToInitialValues( + { supplyId }: { supplyId: string } + ) { + table.options.meta?.removeRowUpdate(supplyId) + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + function handleDeleteSupply({supplyId}: { supplyId: string }) { + + // TODO: add delete shelter supply endpoint here + + toast({ + title: 'Suprimento excluído com sucesso', + }); + } + + return ( +
+
+ + +
+
+ ) +} \ No newline at end of file diff --git a/src/components/ShelterSupplyEditableDataTable/components/data-table-toolbar.tsx b/src/components/ShelterSupplyEditableDataTable/components/data-table-toolbar.tsx new file mode 100644 index 00000000..0f5e7d35 --- /dev/null +++ b/src/components/ShelterSupplyEditableDataTable/components/data-table-toolbar.tsx @@ -0,0 +1,75 @@ +"use client" + +import { Table } from "@tanstack/react-table" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { DataTableFacetedFilter } from "./data-table-faceted-filter" +import { Archive, Search, X } from "lucide-react" +import { priorities } from "./data" +import { useSupplyCategories } from "@/hooks" + +interface DataTableToolbarProps { + table: Table +} + +export function DataTableToolbar({ + table, +}: DataTableToolbarProps) { + const isFiltered = table.getState().columnFilters.length > 0 + + const { data: supplyCategories, loading } = useSupplyCategories(); + + const categories = supplyCategories && supplyCategories.map(category => { + return { + label: category.name, + value: category.name, + icon: Archive + } + }); + + return ( +
+
+
+
+ table.getColumn("supplyName")?.setFilterValue(event.target.value)} + className="h-8 w-full pr-10 pl-10" // Add padding to the right to avoid text overlapping with the button + /> + + {isFiltered && ( + + )} +
+
+ {table.getColumn("priority") && ( + + )} + {categories && !loading && + table.getColumn("supplyCategoryName") && ( + + )} +
+
+
+
+ ) +} \ No newline at end of file diff --git a/src/components/ShelterSupplyEditableDataTable/components/data-table.tsx b/src/components/ShelterSupplyEditableDataTable/components/data-table.tsx new file mode 100644 index 00000000..bdb2a349 --- /dev/null +++ b/src/components/ShelterSupplyEditableDataTable/components/data-table.tsx @@ -0,0 +1,241 @@ +"use client" + +import * as React from "react" +import { + ColumnDef, + ColumnFiltersState, + SortingState, + VisibilityState, + flexRender, + getCoreRowModel, + getFacetedRowModel, + getFacetedUniqueValues, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable, +} from "@tanstack/react-table" +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table" +import { DataTablePagination } from "./data-table-pagination" +import { DataTableToolbar } from "./data-table-toolbar" +import { Button } from "@/components/ui/button" +import { useNavigate, useParams } from "react-router-dom" +import { PlusCircle } from "lucide-react" +import { useToast } from "@/components/ui/use-toast" + +interface DataTableProps { + columns: ColumnDef[] + data: TData[] +} + +export function DataTable({ + columns, + data, +}: DataTableProps) { + const navigate = useNavigate(); + // const { shelterId } = useParams() + const params = useParams(); + const { shelterId = '-1' } = params; + const { toast } = useToast(); + + const [rowSelection, setRowSelection] = React.useState({}) + const [columnVisibility, setColumnVisibility] = + React.useState({}) + const [columnFilters, setColumnFilters] = React.useState( + [] + ) + const [sorting, setSorting] = React.useState([]) + const [updatedRows, setUpdatedRows] = React.useState([]) + + + const table = useReactTable({ + data, + columns, + state: { + sorting, + columnVisibility, + rowSelection, + columnFilters, + }, + enableRowSelection: true, + onRowSelectionChange: setRowSelection, + onSortingChange: setSorting, + onColumnFiltersChange: setColumnFilters, + onColumnVisibilityChange: setColumnVisibility, + getCoreRowModel: getCoreRowModel(), + getFilteredRowModel: getFilteredRowModel(), + getPaginationRowModel: getPaginationRowModel(), + getSortedRowModel: getSortedRowModel(), + getFacetedRowModel: getFacetedRowModel(), + getFacetedUniqueValues: getFacetedUniqueValues(), + meta: { + newData: updatedRows, + updateRowData: (supplyId: string, value: string, columnId: string ) => { + setUpdatedRows((old) => { + + const existingIndex = old.findIndex(item => item.supply.id === supplyId); + const originalData = data.find(item => item.supply.id === supplyId); + + if (!originalData) return old; + + let updatedData: TData[]; + + if (existingIndex > -1) { + updatedData = old.map((item, index) => + index === existingIndex ? { ...item, [columnId]: value } : item + ); + } else { + updatedData = [...old, { ...originalData, [columnId]: value }]; + } + + // Remove the update if it matches the original data + if (JSON.stringify(updatedData[existingIndex]) === JSON.stringify(originalData)) { + return updatedData.filter(item => item.supply.id !== supplyId); + } + + return updatedData; + }); + }, + removeRowUpdate: (supplyId: string) => { + setUpdatedRows((old) => old.filter((updateItem) => updateItem.supply.id !== supplyId)); + }, + }, + }) + + function handleUpdateShelterSupplies(shelterId: string, supplies: TData[]) { + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const dataToUpdate = supplies.map((v) => ({ + label: v.supply.name, + value: v.supply.id, + quantity: v.quantity, + })) + + // TODO: add supply update many endpoint here + + toast({ + title: 'Suprimentos atualizados com sucesso', + }); + + window.location.reload() + } + + + return ( +
+ + {/* TODO: find the best place to this button */} + {/*
+ +
*/} + + + +
+ {/*
+ {JSON.stringify(updatedRows, null, 2)} +
*/} + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ) + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} + + ))} + + )) + ) : ( + + + No results. + + + )} + +
+
+
+
+ +
+ + +
+
+ + +
+ +
+ ) +} \ No newline at end of file diff --git a/src/components/ShelterSupplyEditableDataTable/components/data.tsx b/src/components/ShelterSupplyEditableDataTable/components/data.tsx new file mode 100644 index 00000000..b4fcb933 --- /dev/null +++ b/src/components/ShelterSupplyEditableDataTable/components/data.tsx @@ -0,0 +1,25 @@ +import { CircleIcon } from "lucide-react" + + +export const priorities = [ + { + label: "Necessita urgente", + value: 100, + icon: CircleIcon + }, + { + label: "Precisa", + value: 10, + icon: CircleIcon + }, + { + label: "Disponível para doação", + value: 1, + icon: CircleIcon + }, + { + label: "Não preciso", + value: 0, + icon: CircleIcon + } +] \ No newline at end of file diff --git a/src/components/ShelterSupplyEditableDataTable/components/priority-cell.tsx b/src/components/ShelterSupplyEditableDataTable/components/priority-cell.tsx new file mode 100644 index 00000000..e217ddf4 --- /dev/null +++ b/src/components/ShelterSupplyEditableDataTable/components/priority-cell.tsx @@ -0,0 +1,119 @@ +// import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group" +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + // SelectLabel, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import { cn, getSupplyPriorityProps, priorityOptions, + // separateClasses + } from "@/lib/utils" +import { SupplyPriority } from "@/service/supply/types" +import { useEffect, useState } from "react" +import { CircleIcon } from "lucide-react" +import { getColorClass } from "./utils" + +export const PriorityCell = ({ getValue, row, column, table }: any) => { + const supplyId = row.original.supply.id as string + + const initialPriority: number = getValue() + const [selectedPriority, setSelectedPriority] = useState(initialPriority) + + const { newData } = table.options.meta + const rowIsModified = newData && newData.some((item: any) => item.supply.id === supplyId); + + // is necessaary? + // useEffect(() => { + // setSelectedPriority(initialPriority) + // }, [initialPriority]) + + useEffect(() => { + if (!rowIsModified) { + setSelectedPriority(initialPriority) + } + }, [rowIsModified, initialPriority]) + + function handleUpdatePriority(newPriority: number) { + setSelectedPriority(newPriority) + + table.options.meta?.updateRowData(supplyId, newPriority, column.id) + } + + return ( +
+ {/* Maybe show this component to mobile devices */} + {/* { + const priorityValue = Object.entries(SupplyPriority).find(([, val]) => priorityOptions[val as SupplyPriority] === value) + if (priorityValue) { + handleUpdatePriority(Number(priorityValue[1])) + } + }} + > + {Object.values(SupplyPriority).filter(value => typeof value === 'number').map((value) => { + const { label: labelItem, initials: initialsItem, + // className: circleClassNameItem + } = getSupplyPriorityProps(value as SupplyPriority) + const isEqual = selectedPriority === value + // const result = separateClasses(circleClassNameItem); + const selectedItemClassName = `data-[state=on]:bg-red-500 data-[state=on]:text-white` + // console.log({isEqual}) + // do not working + // const selectedItemClassName = `data-[state=on]:${result.bgClass}` + + return ( + + {initialsItem} + + ) + })} + */} + + +
+ ) +} \ No newline at end of file diff --git a/src/components/ShelterSupplyEditableDataTable/components/quantity-cell.tsx b/src/components/ShelterSupplyEditableDataTable/components/quantity-cell.tsx new file mode 100644 index 00000000..bb6143f1 --- /dev/null +++ b/src/components/ShelterSupplyEditableDataTable/components/quantity-cell.tsx @@ -0,0 +1,55 @@ +import { Input } from "@/components/ui/input" +import { useEffect, useState } from "react" + +export const QuantityCell = ({ getValue, row, column, table }: any) => { + const supplyId = row.original.supply.id as string + const initialQuantity = getValue() + const [newQuantity, setNewQuantity] = useState(initialQuantity) + + const { newData } = table.options.meta + const rowIsModified = newData && newData.some((item: any) => item.supply.id === supplyId); + + // is necessaary? + useEffect(() => { + setNewQuantity(initialQuantity) + }, [initialQuantity]) + + useEffect(() => { + if (!rowIsModified) { + setNewQuantity(initialQuantity) + } + }, [rowIsModified, initialQuantity]) + + + function handleUpdateQuantity(newQuantityToUpdate: number) { + setNewQuantity(newQuantityToUpdate) + + if (initialQuantity === null && newQuantityToUpdate === 0) { + table.options.meta?.updateRowData(supplyId, null, column.id) + } + + table.options.meta?.updateRowData(supplyId, newQuantityToUpdate, column.id) + } + + return ( +
+ handleUpdateQuantity(Number(e.target.value))} + value={newQuantity ? newQuantity : ''} + /> + + {/*
+

+ Anterior: + + {' '}{initialQuantity ? initialQuantity : '0'} + +

+
*/} +
+ ) +} \ No newline at end of file diff --git a/src/components/ShelterSupplyEditableDataTable/components/utils.tsx b/src/components/ShelterSupplyEditableDataTable/components/utils.tsx new file mode 100644 index 00000000..c86af4c6 --- /dev/null +++ b/src/components/ShelterSupplyEditableDataTable/components/utils.tsx @@ -0,0 +1,14 @@ +export const getColorClass = (value: string | number) => { + switch (value) { + case 100: + return "fill-red-300 stroke-red-300"; + case 10: + return "fill-orange-300 stroke-orange-300"; + case 1: + return "fill-green-300 stroke-green-300"; + case 0: + return "fill-slate-300 stroke-slate-300"; + default: + return "fill-transparent"; + } +}; diff --git a/src/components/ui/command.tsx b/src/components/ui/command.tsx new file mode 100644 index 00000000..dd2e876e --- /dev/null +++ b/src/components/ui/command.tsx @@ -0,0 +1,153 @@ +import * as React from "react" +import { type DialogProps } from "@radix-ui/react-dialog" +import { Command as CommandPrimitive } from "cmdk" +import { Search } from "lucide-react" + +import { cn } from "@/lib/utils" +import { Dialog, DialogContent } from "@/components/ui/dialog" + +const Command = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +Command.displayName = CommandPrimitive.displayName + +interface CommandDialogProps extends DialogProps {} + +const CommandDialog = ({ children, ...props }: CommandDialogProps) => { + return ( + + + + {children} + + + + ) +} + +const CommandInput = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( +
+ + +
+)) + +CommandInput.displayName = CommandPrimitive.Input.displayName + +const CommandList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +CommandList.displayName = CommandPrimitive.List.displayName + +const CommandEmpty = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>((props, ref) => ( + +)) + +CommandEmpty.displayName = CommandPrimitive.Empty.displayName + +const CommandGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +CommandGroup.displayName = CommandPrimitive.Group.displayName + +const CommandSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +CommandSeparator.displayName = CommandPrimitive.Separator.displayName + +const CommandItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +CommandItem.displayName = CommandPrimitive.Item.displayName + +const CommandShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ) +} +CommandShortcut.displayName = "CommandShortcut" + +export { + Command, + CommandDialog, + CommandInput, + CommandList, + CommandEmpty, + CommandGroup, + CommandItem, + CommandShortcut, + CommandSeparator, +} diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx new file mode 100644 index 00000000..769ff7aa --- /dev/null +++ b/src/components/ui/dropdown-menu.tsx @@ -0,0 +1,198 @@ +import * as React from "react" +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" +import { Check, ChevronRight, Circle } from "lucide-react" + +import { cn } from "@/lib/utils" + +const DropdownMenu = DropdownMenuPrimitive.Root + +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger + +const DropdownMenuGroup = DropdownMenuPrimitive.Group + +const DropdownMenuPortal = DropdownMenuPrimitive.Portal + +const DropdownMenuSub = DropdownMenuPrimitive.Sub + +const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup + +const DropdownMenuSubTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, children, ...props }, ref) => ( + + {children} + + +)) +DropdownMenuSubTrigger.displayName = + DropdownMenuPrimitive.SubTrigger.displayName + +const DropdownMenuSubContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSubContent.displayName = + DropdownMenuPrimitive.SubContent.displayName + +const DropdownMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)) +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName + +const DropdownMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + +)) +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName + +const DropdownMenuCheckboxItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, checked, ...props }, ref) => ( + + + + + + + {children} + +)) +DropdownMenuCheckboxItem.displayName = + DropdownMenuPrimitive.CheckboxItem.displayName + +const DropdownMenuRadioItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)) +DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName + +const DropdownMenuLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + +)) +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName + +const DropdownMenuSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName + +const DropdownMenuShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ) +} +DropdownMenuShortcut.displayName = "DropdownMenuShortcut" + +export { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuGroup, + DropdownMenuPortal, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuRadioGroup, +} diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx new file mode 100644 index 00000000..bbba7e0e --- /dev/null +++ b/src/components/ui/popover.tsx @@ -0,0 +1,29 @@ +import * as React from "react" +import * as PopoverPrimitive from "@radix-ui/react-popover" + +import { cn } from "@/lib/utils" + +const Popover = PopoverPrimitive.Root + +const PopoverTrigger = PopoverPrimitive.Trigger + +const PopoverContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( + + + +)) +PopoverContent.displayName = PopoverPrimitive.Content.displayName + +export { Popover, PopoverTrigger, PopoverContent } diff --git a/src/components/ui/switch.tsx b/src/components/ui/switch.tsx new file mode 100644 index 00000000..aa58baa2 --- /dev/null +++ b/src/components/ui/switch.tsx @@ -0,0 +1,27 @@ +import * as React from "react" +import * as SwitchPrimitives from "@radix-ui/react-switch" + +import { cn } from "@/lib/utils" + +const Switch = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +Switch.displayName = SwitchPrimitives.Root.displayName + +export { Switch } diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx new file mode 100644 index 00000000..7f3502f8 --- /dev/null +++ b/src/components/ui/table.tsx @@ -0,0 +1,117 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +const Table = React.forwardRef< + HTMLTableElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+ + +)) +Table.displayName = "Table" + +const TableHeader = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableHeader.displayName = "TableHeader" + +const TableBody = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableBody.displayName = "TableBody" + +const TableFooter = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + tr]:last:border-b-0", + className + )} + {...props} + /> +)) +TableFooter.displayName = "TableFooter" + +const TableRow = React.forwardRef< + HTMLTableRowElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableRow.displayName = "TableRow" + +const TableHead = React.forwardRef< + HTMLTableCellElement, + React.ThHTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +TableHead.displayName = "TableHead" + +const TableCell = React.forwardRef< + HTMLTableCellElement, + React.TdHTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableCell.displayName = "TableCell" + +const TableCaption = React.forwardRef< + HTMLTableCaptionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +TableCaption.displayName = "TableCaption" + +export { + Table, + TableHeader, + TableBody, + TableFooter, + TableHead, + TableRow, + TableCell, + TableCaption, +} diff --git a/src/components/ui/toggle-group.tsx b/src/components/ui/toggle-group.tsx new file mode 100644 index 00000000..19505f9a --- /dev/null +++ b/src/components/ui/toggle-group.tsx @@ -0,0 +1,59 @@ +import * as React from "react" +import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group" +import { VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" +import { toggleVariants } from "@/components/ui/toggle" + +const ToggleGroupContext = React.createContext< + VariantProps +>({ + size: "default", + variant: "default", +}) + +const ToggleGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, variant, size, children, ...props }, ref) => ( + + + {children} + + +)) + +ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName + +const ToggleGroupItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, children, variant, size, ...props }, ref) => { + const context = React.useContext(ToggleGroupContext) + + return ( + + {children} + + ) +}) + +ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName + +export { ToggleGroup, ToggleGroupItem } diff --git a/src/components/ui/toggle.tsx b/src/components/ui/toggle.tsx new file mode 100644 index 00000000..9ecac28e --- /dev/null +++ b/src/components/ui/toggle.tsx @@ -0,0 +1,43 @@ +import * as React from "react" +import * as TogglePrimitive from "@radix-ui/react-toggle" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const toggleVariants = cva( + "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground", + { + variants: { + variant: { + default: "bg-transparent", + outline: + "border border-input bg-transparent hover:bg-accent hover:text-accent-foreground", + }, + size: { + default: "h-10 px-3", + sm: "h-9 px-2.5", + lg: "h-11 px-5", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +const Toggle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, variant, size, ...props }, ref) => ( + +)) + +Toggle.displayName = TogglePrimitive.Root.displayName + +export { Toggle, toggleVariants } diff --git a/src/hooks/useShelter/types.ts b/src/hooks/useShelter/types.ts index 22c3bd67..fea994a6 100644 --- a/src/hooks/useShelter/types.ts +++ b/src/hooks/useShelter/types.ts @@ -3,6 +3,11 @@ export enum ShelterCategory { DistributionCenter = 'DistributionCenter', } +export enum ShelterCategoryName { + Shelter = 'Abrigo', + DistributionCenter = 'Centro de Distribuição', +} + export interface IUseShelterData { id: string; name: string; diff --git a/src/lib/utils.ts b/src/lib/utils.ts index c12c1b59..51446036 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -26,12 +26,12 @@ function getAvailabilityProps(props: { } else if (capacity && (shelteredPeople || shelteredPeople === 0)) { if (shelteredPeople < capacity) return { - availability: 'Abrigo disponível', + availability: 'disponível', className: 'text-green-600', }; else return { - availability: 'Abrigo lotado', + availability: 'lotado', className: 'text-red-400', }; } else @@ -55,21 +55,25 @@ function getSupplyPriorityProps(priority: SupplyPriority) { return { label, className: 'bg-gray-200 text-gray-800', + initials: 'N' }; case SupplyPriority.Remaining: return { label, className: 'bg-light-green text-green-800', + initials: 'D' }; case SupplyPriority.Needing: return { label, className: 'bg-light-orange text-orange-800', + initials: 'P' }; case SupplyPriority.Urgent: return { label, className: 'bg-light-red text-red-800', + initials: 'U' }; } } @@ -150,6 +154,27 @@ function checkIsNull(v?: any | null) { return v !== null && v !== undefined; } +function separateClasses(classString: string): { bgClass: string; textClass: string } { + const classes = classString.split(' '); + let bgClass = ''; + let textClass = ''; + + classes.forEach((cls) => { + switch (true) { + case cls.startsWith('bg-'): + bgClass = cls; + break; + case cls.startsWith('text-'): + textClass = cls; + break; + default: + break; + } + }); + + return { bgClass, textClass }; +} + export { cn, getAvailabilityProps, @@ -160,4 +185,5 @@ export { removeDuplicatesByField, normalizedCompare, checkIsNull, + separateClasses }; diff --git a/src/pages/Shelter/Shelter.tsx b/src/pages/Shelter/Shelter.tsx index 78fea039..45d5b27e 100644 --- a/src/pages/Shelter/Shelter.tsx +++ b/src/pages/Shelter/Shelter.tsx @@ -11,7 +11,10 @@ import { } from '@/components'; import { useShelter } from '@/hooks'; import { IShelterAvailabilityProps } from '@/pages/Home/components/ShelterListItem/types'; -import { cn, getAvailabilityProps, group } from '@/lib/utils'; +import { + cn, getAvailabilityProps, + group +} from '@/lib/utils'; import { Button } from '@/components/ui/button'; import { ShelterCategoryItems } from './components'; import { @@ -23,7 +26,11 @@ import { VerifiedBadge } from '@/components/VerifiedBadge/VerifiedBadge.tsx'; import { ShelterSupplyServices } from '@/service'; import { useToast } from '@/components/ui/use-toast'; import { clearCache } from '@/api/cache'; -import { ShelterCategory } from '@/hooks/useShelter/types'; +import { ShelterCategory, ShelterCategoryName } from '@/hooks/useShelter/types'; +import { DataTable } from '../../components/ShelterSupplyEditableDataTable/components/data-table'; +import { columns } from '../../components/ShelterSupplyEditableDataTable/components/columns'; +import { Label } from '@/components/ui/label'; +import { Switch } from '@/components/ui/switch'; const Shelter = () => { const params = useParams(); @@ -46,6 +53,7 @@ const Shelter = () => { })), })); }, [shelter?.shelterSupplies]); + const { availability, className: availabilityClassName } = useMemo( () => @@ -90,6 +98,11 @@ const Shelter = () => { }); }, [refresh, selectedTags, shelterId, toast]); + const [showEditableTableData, setShowEditableTableData] = useState(false) + const shelterSupplyData = useMemo(() => { + return shelter?.shelterSupplies ?? []; + }, [shelter?.shelterSupplies]); + if (loading) return ; return ( @@ -107,49 +120,77 @@ const Shelter = () => { } /> -
-
-

- {shelter.name} -

- {shelter.verified && } -
-
+
+
+
+ {ShelterCategoryName[shelter.category]} + {shelter.verified && } +

{availability}

+
+
+

+ {shelter.name} +

-
+
-
+ {shelter.updatedAt && ( +
+ + Atualizado em {format(shelter.updatedAt, 'dd/MM/yyyy HH:mm')} + +
+ )} +

Itens do abrigo

-
- + + */}
-
- {shelterCategories.map((categoryProps, idx) => ( +
+ {!showEditableTableData && shelterCategories.map((categoryProps, idx) => ( { {...categoryProps} /> ))} + {shelterSupplyData && showEditableTableData && + + }
- {shelter.updatedAt && ( -
- - Atualizado em {format(shelter.updatedAt, 'dd/MM/yyyy HH:mm')} - -
- )} + +