From 3a893cd03cd359aeff8a8f736d17074b45f8c544 Mon Sep 17 00:00:00 2001 From: Alexis Gousseau Date: Thu, 12 Feb 2026 15:42:22 +0100 Subject: [PATCH] =?UTF-8?q?fix:=20=F0=9F=9A=A8=20resolve=20last=20eslint?= =?UTF-8?q?=20errors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintrc.cjs | 5 + .../Controllers/Back/DocumentController.php | 11 + package-lock.json | 29 +- package.json | 5 +- resources/sass/back/inputs/_color.scss | 101 +++ .../ts/back/Components/ApplicationLogo.tsx | 22 +- .../Components/AssociatedCompaniesList.tsx | 33 +- .../BtnOptionnalLinkWithTooltip.tsx | 49 +- .../Documents/AssociatedDocumentList.tsx | 46 +- .../ts/back/Components/Documents/Filters.tsx | 8 +- .../Components/DynamicPages/DynamicInputs.tsx | 64 +- .../Components/DynamicPages/DynamicList.tsx | 479 ++++++------ .../DynamicPages/DynamicPageDetails.tsx | 229 +++--- .../DynamicWrapperModelSelect.tsx | 74 ++ .../ts/back/Components/GoToFrontView.tsx | 22 +- resources/ts/back/Components/InputError.tsx | 4 +- .../Components/Inputs/CompleteInputFile.tsx | 147 ++-- .../ts/back/Components/Inputs/EnumSelect.tsx | 20 +- .../back/Components/Inputs/ImageCropper.tsx | 13 +- .../ts/back/Components/Inputs/ModelSelect.tsx | 701 +++++++++--------- .../ts/back/Components/Inputs/MultiSelect.tsx | 13 +- .../Components/Inputs/SimpleColorPicker.tsx | 120 +-- .../Components/Inputs/SimpleDatePicker.tsx | 15 +- .../back/Components/Inputs/SimpleNumber.tsx | 1 + .../back/Components/Inputs/SimpleSwitch.tsx | 2 +- .../back/Components/Inputs/SimpleVimeoId.tsx | 4 +- .../Components/Inputs/SimpleYoutubeId.tsx | 4 +- .../back/Components/Inputs/Slugifycator.tsx | 18 +- .../Components/Inputs/StaticRouteSelect.tsx | 19 +- .../Inputs/Wysiwyg/Extensions/Image/Image.tsx | 18 +- .../Wysiwyg/Extensions/Image/ImageButton.tsx | 150 ++-- .../Wysiwyg/Extensions/Image/ImageInputs.tsx | 7 +- .../Extensions/Image/ImageNodeView.tsx | 92 +-- .../Wysiwyg/Extensions/Image/image.d.ts | 2 + .../Inputs/Wysiwyg/Extensions/Link/Link.tsx | 72 +- .../Wysiwyg/Extensions/Link/LinkButton.tsx | 174 ++--- .../Inputs/Wysiwyg/Extensions/Link/link.d.ts | 2 + .../Extensions/Link/tiptap-events.d.ts | 1 + .../Extensions/Replace/FindReplaceButton.tsx | 44 +- .../Extensions/ShortCuts/ShortcutBoundary.tsx | 4 +- .../Wysiwyg/Extensions/Table/TableButton.tsx | 45 +- .../Inputs/Wysiwyg/SimpleWysiwyg.tsx | 15 +- .../Components/Inputs/Wysiwyg/Toolbar.tsx | 57 +- .../Components/Inputs/Wysiwyg/tiptap.d.ts | 1 + .../ts/back/Components/Layout/AppSettings.tsx | 12 +- .../ts/back/Components/Layout/AppSidebar.tsx | 516 +++++++------ resources/ts/back/Components/ModelActions.tsx | 524 +++++++------ .../Components/News/AssociatedNewsList.tsx | 51 +- .../Components/PageConfigs/AddNewFieldBtn.tsx | 5 +- .../Components/PageConfigs/ConfigDetails.tsx | 8 +- .../Components/PageConfigs/DynamicFields.tsx | 39 +- .../Components/PageConfigs/FieldGroup.tsx | 21 +- .../PageConfigs/ImportConfigJson.tsx | 131 ++-- .../Pagination/PaginationWrapper.tsx | 19 +- resources/ts/back/Components/SubmitButton.tsx | 3 +- .../ts/back/Components/Table/DetailsTable.tsx | 5 +- .../Table/RenderTranslationCell.tsx} | 30 +- resources/ts/back/Components/WordCounter.tsx | 1 + .../ts/back/Layouts/AuthenticatedLayout.tsx | 74 +- resources/ts/back/Layouts/GuestLayout.tsx | 2 +- .../ts/back/Pages/ActivityLogs/Index.tsx | 221 +++--- resources/ts/back/Pages/ActivityLogs/Show.tsx | 111 +-- resources/ts/back/Pages/Companies/Create.tsx | 3 +- resources/ts/back/Pages/Companies/Edit.tsx | 3 +- resources/ts/back/Pages/Companies/Form.tsx | 9 +- resources/ts/back/Pages/Companies/Index.tsx | 182 ++--- resources/ts/back/Pages/Companies/Show.tsx | 337 ++++----- .../back/Pages/CompanyActivities/Create.tsx | 7 +- .../ts/back/Pages/CompanyActivities/Edit.tsx | 7 +- .../ts/back/Pages/CompanyActivities/Form.tsx | 3 +- .../ts/back/Pages/CompanyActivities/Index.tsx | 121 +-- .../ts/back/Pages/CompanyActivities/Show.tsx | 176 +++-- .../ts/back/Pages/CompanySectors/Create.tsx | 7 +- .../ts/back/Pages/CompanySectors/Edit.tsx | 7 +- .../ts/back/Pages/CompanySectors/Form.tsx | 3 +- .../ts/back/Pages/CompanySectors/Index.tsx | 195 ++--- .../ts/back/Pages/CompanySectors/Show.tsx | 151 ++-- .../ts/back/Pages/ContactCount/Index.tsx | 52 +- resources/ts/back/Pages/Document/Create.tsx | 3 +- resources/ts/back/Pages/Document/Edit.tsx | 9 +- resources/ts/back/Pages/Document/Form.tsx | 62 +- resources/ts/back/Pages/Document/Index.tsx | 148 ++-- resources/ts/back/Pages/Document/Show.tsx | 506 +++++++------ .../ts/back/Pages/DocumentCategory/Create.tsx | 7 +- .../ts/back/Pages/DocumentCategory/Edit.tsx | 9 +- .../ts/back/Pages/DocumentCategory/Form.tsx | 3 +- .../ts/back/Pages/DocumentCategory/Index.tsx | 163 ++-- .../ts/back/Pages/DocumentCategory/Show.tsx | 110 +-- .../ts/back/Pages/DocumentTag/Create.tsx | 7 +- resources/ts/back/Pages/DocumentTag/Edit.tsx | 7 +- resources/ts/back/Pages/DocumentTag/Form.tsx | 6 +- resources/ts/back/Pages/DocumentTag/Index.tsx | 121 ++- resources/ts/back/Pages/DocumentTag/Show.tsx | 152 ++-- .../ts/back/Pages/DynamicPages/Create.tsx | 7 +- resources/ts/back/Pages/DynamicPages/Edit.tsx | 7 +- resources/ts/back/Pages/DynamicPages/Form.tsx | 101 ++- .../ts/back/Pages/DynamicPages/Index.tsx | 144 ++-- resources/ts/back/Pages/DynamicPages/Show.tsx | 117 +-- resources/ts/back/Pages/News/Create.tsx | 3 +- resources/ts/back/Pages/News/Edit.tsx | 3 +- resources/ts/back/Pages/News/Form.tsx | 23 +- resources/ts/back/Pages/News/Index.tsx | 173 ++--- resources/ts/back/Pages/News/Show.tsx | 185 ++--- .../ts/back/Pages/NewsCategory/Create.tsx | 7 +- resources/ts/back/Pages/NewsCategory/Edit.tsx | 7 +- resources/ts/back/Pages/NewsCategory/Form.tsx | 3 +- .../ts/back/Pages/NewsCategory/Index.tsx | 156 ++-- resources/ts/back/Pages/NewsCategory/Show.tsx | 110 +-- resources/ts/back/Pages/NewsTag/Create.tsx | 3 +- resources/ts/back/Pages/NewsTag/Edit.tsx | 3 +- resources/ts/back/Pages/NewsTag/Form.tsx | 3 +- resources/ts/back/Pages/NewsTag/Index.tsx | 108 +-- resources/ts/back/Pages/NewsTag/Show.tsx | 172 ++--- .../ts/back/Pages/PageConfigs/Create.tsx | 3 +- resources/ts/back/Pages/PageConfigs/Edit.tsx | 3 +- resources/ts/back/Pages/PageConfigs/Form.tsx | 73 +- resources/ts/back/Pages/PageConfigs/Index.tsx | 154 ++-- resources/ts/back/Pages/PageConfigs/Show.tsx | 139 ++-- resources/ts/back/Pages/Users/Form.tsx | 31 +- resources/ts/back/Pages/Users/Index.tsx | 74 +- resources/ts/back/Pages/Users/Show.tsx | 55 +- resources/ts/back/app.tsx | 1 + resources/ts/back/types/activity-logs.d.ts | 16 + resources/ts/back/types/dynamic-pages.d.ts | 76 ++ resources/ts/back/types/global.d.ts | 299 +------- resources/ts/back/types/i18n.d.ts | 1 + resources/ts/back/types/others.d.ts | 148 ++++ resources/ts/front/base/common.ts | 7 +- resources/ts/front/index.d.ts | 1 + resources/ts/front/modules/route.ts | 2 + resources/ts/hooks/use-crud-messages.tsx | 102 ++- resources/ts/hooks/use-crud-submit.tsx | 40 +- resources/ts/hooks/use-recaptcha.tsx | 7 +- resources/ts/lib/mapEnumOptions.ts | 7 +- resources/ts/lib/mapPivotOptions.ts | 78 +- resources/ts/lib/utils.ts | 28 + routes/web-back.php | 2 + 137 files changed, 5239 insertions(+), 4734 deletions(-) create mode 100644 resources/sass/back/inputs/_color.scss create mode 100644 resources/ts/back/Components/DynamicPages/DynamicWrapperModelSelect.tsx rename resources/ts/{lib/translationCellFallback.tsx => back/Components/Table/RenderTranslationCell.tsx} (75%) create mode 100644 resources/ts/back/types/activity-logs.d.ts create mode 100644 resources/ts/back/types/dynamic-pages.d.ts create mode 100644 resources/ts/back/types/i18n.d.ts create mode 100644 resources/ts/back/types/others.d.ts diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 7dee6d3b..5411e55a 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -66,5 +66,10 @@ module.exports = { }, ], 'prettier/prettier': 'error', + "no-console": [ + "warn", { + allow: ["warn", "error"], + }, + ], }, } diff --git a/app/Http/Controllers/Back/DocumentController.php b/app/Http/Controllers/Back/DocumentController.php index 6a8f0f86..88908b5e 100644 --- a/app/Http/Controllers/Back/DocumentController.php +++ b/app/Http/Controllers/Back/DocumentController.php @@ -62,6 +62,7 @@ class DocumentController extends Controller /** Custom pagination */ $docModels = $this->paginate($query); + return Inertia::render('Document/Index', [ 'documentsModel' => CommonResource::collection($docModels), 'searchFields' => $searchFields, @@ -160,6 +161,16 @@ class DocumentController extends Controller }); } + /** + * Duplicate the specified resource. + * + * @param \App\Models\Document $document + * @return \Inertia\Response + */ + public function duplicate(Document $document): \Inertia\Response + { + return $this->create($document->replicate()); + } /** * Destroy the specified resource. diff --git a/package-lock.json b/package-lock.json index 18aece12..4c2e8353 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,7 @@ "@tiptap/extension-image": "^3.17.0", "@tiptap/extension-invisible-characters": "^3.17.1", "@tiptap/extension-italic": "^3.17.0", + "@tiptap/extension-link": "^3.19.0", "@tiptap/extension-list": "^3.17.1", "@tiptap/extension-ordered-list": "^3.17.0", "@tiptap/extension-placeholder": "^3.17.0", @@ -47,6 +48,7 @@ "@types/react-dom": "^19.1.7", "axios": "^1.13.2", "chart.js": "^4.5.1", + "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", "date-fns": "^4.1.0", @@ -55,6 +57,7 @@ "laravel-vite-plugin": "^1.3.0", "lucide-react": "^0.503.0", "motion": "^12.23.12", + "prosemirror-state": "^1.4.4", "react-color": "^2.19.3", "react-day-picker": "^9.11.1", "react-dropzone": "^14.3.8", @@ -86,7 +89,6 @@ "@vitejs/plugin-react": "^4.7.0", "autoprefixer": "^10.4.21", "baseline-browser-mapping": "^2.9.19", - "class-variance-authority": "^0.7.1", "concurrently": "^9.2.0", "eslint": "^8.57.1", "eslint-config-airbnb": "^19.0.4", @@ -4023,16 +4025,16 @@ } }, "node_modules/@tiptap/core": { - "version": "3.17.1", - "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.17.1.tgz", - "integrity": "sha512-f8hB9MzXqsuXoF9qXEDEH5Fb3VgwhEFMBMfk9EKN88l5adri6oM8mt2XOWVxVVssjpEW0177zXSLPKWzoS/vrw==", + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.19.0.tgz", + "integrity": "sha512-bpqELwPW+DG8gWiD8iiFtSl4vIBooG5uVJod92Qxn3rA9nFatyXRr4kNbMJmOZ66ezUvmCjXVe/5/G4i5cyzKA==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/pm": "^3.17.1" + "@tiptap/pm": "^3.19.0" } }, "node_modules/@tiptap/extension-blockquote": { @@ -4282,9 +4284,9 @@ } }, "node_modules/@tiptap/extension-link": { - "version": "3.17.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-3.17.0.tgz", - "integrity": "sha512-qGL/s7RxgWL1dY3ZPsPtBYQsfiNpwnqoK5dVQ7NbqjphK1EF/Q4uBRVM3CKMZiRBHdRlSVZewBycHs4uyAKPbA==", + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-3.19.0.tgz", + "integrity": "sha512-HEGDJnnCPfr7KWu7Dsq+eRRe/mBCsv6DuI+7fhOCLDJjjKzNgrX2abbo/zG3D/4lCVFaVb+qawgJubgqXR/Smw==", "license": "MIT", "dependencies": { "linkifyjs": "^4.3.2" @@ -4294,8 +4296,8 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.17.0", - "@tiptap/pm": "^3.17.0" + "@tiptap/core": "^3.19.0", + "@tiptap/pm": "^3.19.0" } }, "node_modules/@tiptap/extension-list": { @@ -4539,9 +4541,9 @@ } }, "node_modules/@tiptap/pm": { - "version": "3.17.1", - "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.17.1.tgz", - "integrity": "sha512-UyVLkN8axV/zop6Se2DCBJRu5DM21X0XEQvwEC5P/vk8eC9OcQZ3FLtxeYy2ZjpAZUzBGLw0/BGsmEip/n7olw==", + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.19.0.tgz", + "integrity": "sha512-789zcnM4a8OWzvbD2DL31d0wbSm9BVeO/R7PLQwLIGysDI3qzrcclyZ8yhqOEVuvPitRRwYLq+mY14jz7kY4cw==", "license": "MIT", "dependencies": { "prosemirror-changeset": "^2.3.0", @@ -6088,7 +6090,6 @@ "version": "0.7.1", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", - "dev": true, "license": "Apache-2.0", "dependencies": { "clsx": "^2.1.1" diff --git a/package.json b/package.json index 354ab45f..82df468c 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "eslint-fix": "node ./node_modules/.bin/eslint --config './.eslintrc.cjs' --max-warnings 0 resources/ts --ext .ts,.tsx --fix", "eslint-ci": "node ./node_modules/.bin/eslint --config './.eslintrc.cjs' --max-warnings 0 resources/ts --ext .ts,.tsx -f junit", "stylelint": "node ./node_modules/.bin/stylelint --config './.stylelintrc.json' --max-warnings 0 '**/resources/**/*.scss'", + "stylelint-fix": "node ./node_modules/.bin/stylelint --config './.stylelintrc.json' --max-warnings 0 '**/resources/**/*.scss' --fix", "stylelint-ci": "node ./node_modules/.bin/stylelint --config './.stylelintrc.json' --max-warnings 0 '**/resources/**/*.scss' -f json" }, "devDependencies": { @@ -33,7 +34,6 @@ "@vitejs/plugin-react": "^4.7.0", "autoprefixer": "^10.4.21", "baseline-browser-mapping": "^2.9.19", - "class-variance-authority": "^0.7.1", "concurrently": "^9.2.0", "eslint": "^8.57.1", "eslint-config-airbnb": "^19.0.4", @@ -86,6 +86,7 @@ "@tiptap/extension-image": "^3.17.0", "@tiptap/extension-invisible-characters": "^3.17.1", "@tiptap/extension-italic": "^3.17.0", + "@tiptap/extension-link": "^3.19.0", "@tiptap/extension-list": "^3.17.1", "@tiptap/extension-ordered-list": "^3.17.0", "@tiptap/extension-placeholder": "^3.17.0", @@ -105,6 +106,7 @@ "@types/react-dom": "^19.1.7", "axios": "^1.13.2", "chart.js": "^4.5.1", + "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", "date-fns": "^4.1.0", @@ -113,6 +115,7 @@ "laravel-vite-plugin": "^1.3.0", "lucide-react": "^0.503.0", "motion": "^12.23.12", + "prosemirror-state": "^1.4.4", "react-color": "^2.19.3", "react-day-picker": "^9.11.1", "react-dropzone": "^14.3.8", diff --git a/resources/sass/back/inputs/_color.scss b/resources/sass/back/inputs/_color.scss new file mode 100644 index 00000000..15f7746d --- /dev/null +++ b/resources/sass/back/inputs/_color.scss @@ -0,0 +1,101 @@ +.sketch-picker { + display: flex; + width: 100% !important; + flex-direction: column; + padding: 0 !important; + border: 0 !important; + box-shadow: none !important; +} + +.sketch-picker, +.sketch-picker .flexbox-fix { + gap: 0.375rem; +} + +.sketch-picker > div, +.sketch-picker .flexbox-fix > div { + padding: 0 !important; + margin: 0 !important; +} + +.sketch-picker > div:first-child { + border-radius: calc(var(--radius) - 2px); + aspect-ratio: 1 / 1; +} + +.sketch-picker > div:last-child { + justify-content: space-between; + padding-top: 0.375rem !important; +} + +.sketch-picker > div:nth-child(3) > div { + flex: auto !important; +} + +.sketch-picker > div:last-child > div { + width: 20px !important; + height: 20px !important; +} + +.sketch-picker > div:last-child > div > span > div { + border-radius: calc(var(--radius) - 2px) !important; + box-shadow: none !important; + outline: 1px solid hsl(var(--input)) !important; +} + +.sketch-picker > div:nth-child(2) > div:first-child { + display: flex; + flex-direction: column; + gap: 0.375rem; +} + +.sketch-picker > div:nth-child(2) > div:last-child { + display: none; +} + +.sketch-picker > div:nth-child(3) > div:first-child { + width: 16rem; +} + +.sketch-picker > div:nth-child(2) > div:first-child > div { + height: 20px !important; + border-radius: calc(var(--radius) - 2px); + margin: 0 !important; + cursor: pointer; + outline: 1px solid hsl(var(--input)); +} + +/* stylelint-disable-next-line selector-max-compound-selectors */ +.sketch-picker > div:first-child > div > div > div:last-child > div { + width: 10px !important; + height: 10px !important; + border-radius: 50% !important; + background-color: #fff; + box-shadow: none !important; + transform: translate(-5px, -5px) !important; +} + +/* stylelint-disable-next-line selector-max-compound-selectors */ +.sketch-picker > div:nth-child(2) > div:first-child > div > div > div > div > div { + width: 0.5rem !important; + height: 20px !important; + border: 1px hsl(var(--input)) solid; + border-radius: 0 !important; + margin-top: 0 !important; + background: #fff !important; + box-shadow: none !important; + transform: translateX(-4px) !important; +} + +.sketch-picker input { + width: 100% !important; + height: 2.5rem; + padding: 0.5rem 0.75rem !important; + border: 1px hsl(var(--input)) solid !important; + border-radius: calc(var(--radius) - 2px) !important; + background-color: hsl(var(--background)); + box-shadow: none !important; + color: var(--foreground); + outline: none; + text-align: center; +} diff --git a/resources/ts/back/Components/ApplicationLogo.tsx b/resources/ts/back/Components/ApplicationLogo.tsx index 98ab0d0e..1e7286ba 100644 --- a/resources/ts/back/Components/ApplicationLogo.tsx +++ b/resources/ts/back/Components/ApplicationLogo.tsx @@ -12,19 +12,27 @@ type ApplicationLogoProps = { isCollapsed?: boolean; }; +/** + * Get the logo. + * + * @param isCollapsed boolean + * @param forceLight boolean + * @param theme string + * @return string + */ +function getLogo(isCollapsed: boolean, forceLight: boolean, theme: string) { + if (isCollapsed) return logoTinyUwilCMS; + if (forceLight || theme === "dark") return logoUwilCMSLight; + return logoUwilCMSDark; +} + export default function ApplicationLogo({ forceLight = false, isCollapsed = false, }: ApplicationLogoProps) { const { theme } = useTheme(); const wrapperClasses = isCollapsed ? "h-8" : "w-full max-w-[10rem] mx-auto"; - const logo = isCollapsed - ? logoTinyUwilCMS - : forceLight - ? logoUwilCMSLight - : theme === "dark" - ? logoUwilCMSLight - : logoUwilCMSDark; + const logo = getLogo(isCollapsed, forceLight, theme); return ( { + return [ { accessorKey: "name", header: ({ column }: { column: { id: string } }) => ( ( )} {str( - useTrans( + GetTranslation( row.getValue("published") ? "back.yes" : "back.no", ), ) @@ -70,7 +72,9 @@ function AssociatedCompaniesList(props: Props) { accessorKey: "updated_at", header: ({ column }: { column: { id: string } }) => ( }) => ( - {useFormatedDate(row.getValue("updated_at"))} + {GetFormatedDate(row.getValue("updated_at"))} ), }, ]; +}; + +export default function AssociatedCompaniesList(props: Props) { + const { companies } = props; + const columns = getColumns(); return ( <> @@ -110,5 +119,3 @@ function AssociatedCompaniesList(props: Props) { ); } - -export default AssociatedCompaniesList; diff --git a/resources/ts/back/Components/BtnOptionnalLinkWithTooltip.tsx b/resources/ts/back/Components/BtnOptionnalLinkWithTooltip.tsx index 4018c32b..f4eebe55 100644 --- a/resources/ts/back/Components/BtnOptionnalLinkWithTooltip.tsx +++ b/resources/ts/back/Components/BtnOptionnalLinkWithTooltip.tsx @@ -1,6 +1,11 @@ // * Packages import { Link } from "@inertiajs/react"; -import { CSSProperties, MouseEventHandler, ReactNode } from "react"; +import { + ButtonHTMLAttributes, + CSSProperties, + MouseEventHandler, + ReactNode, +} from "react"; import { VariantProps } from "class-variance-authority"; // * Components shadcn import { Button, buttonVariants } from "@/components/ui/button"; @@ -9,12 +14,10 @@ import { TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip"; -// * Other -import { cn } from "@/lib/utils"; type ButtonVariant = VariantProps["variant"]; type ButtonSize = VariantProps["size"]; -type ButtonType = React.ButtonHTMLAttributes["type"]; +type ButtonType = ButtonHTMLAttributes["type"]; type BtnOptionnalLinkWithTooltipProps = { link?: string | null; @@ -27,7 +30,7 @@ type BtnOptionnalLinkWithTooltipProps = { externalLink?: boolean; disabled?: boolean; style?: CSSProperties; - onClick?: (e: React.MouseEvent) => void; + onClick?: MouseEventHandler; }; export default function BtnOptionnalLinkWithTooltip({ @@ -44,6 +47,22 @@ export default function BtnOptionnalLinkWithTooltip({ onClick, ...props }: BtnOptionnalLinkWithTooltipProps) { + let innerContent: ReactNode = content; + + if (link !== null) { + if (externalLink) { + innerContent = disabled ? ( + + ) : ( + + {content} + + ); + } else { + innerContent = {content}; + } + } + return ( @@ -56,26 +75,10 @@ export default function BtnOptionnalLinkWithTooltip({ disabled={disabled} onClick={onClick} style={style} + // eslint-disable-next-line react/jsx-props-no-spreading {...props} > - {link !== null ? ( - externalLink ? ( - - {content} - - ) : ( - {content} - ) - ) : ( - content - )} + {innerContent} {tooltipContent} diff --git a/resources/ts/back/Components/Documents/AssociatedDocumentList.tsx b/resources/ts/back/Components/Documents/AssociatedDocumentList.tsx index 762d17cd..f11df65c 100644 --- a/resources/ts/back/Components/Documents/AssociatedDocumentList.tsx +++ b/resources/ts/back/Components/Documents/AssociatedDocumentList.tsx @@ -7,43 +7,39 @@ import str from "@/hooks/use-string"; import DataTable from "@/back/Components/Table/DataTable"; import useTrans from "@/hooks/use-translations"; import DataTableFilterButton from "@/back/Components/Table/DataTableFilterButton"; -import { - AppEnv, - DocumentModel, - Resource, - TranslationField, -} from "@/back/types/global"; -import { Views } from "@/lib/utils"; -import { renderTranslationCell } from "@/lib/translationCellFallback"; +import RenderTranslationCell from "@/back/Components/Table/RenderTranslationCell"; +import { AppEnv, Resource } from "@/back/types/global"; +import { TranslationField } from "@/back/types/i18n"; +import { DocumentModel } from "@/back/types/others"; +import { GetTranslation, Views } from "@/lib/utils"; interface Props { documents: Resource; } -function AssociatedDocumentList(props: Props) { - const { documents } = props; - const { appEnv } = usePage().props as unknown as { - appEnv: AppEnv; - }; - const documentColumns = [ + +const getColumns = (appEnv: AppEnv) => { + return [ { accessorKey: "name", header: ({ column }: { column: { id: string } }) => ( ), - cell: ({ row }: { row: Row }) => { - return renderTranslationCell(row.getValue("name")); - }, + cell: ({ row }: { row: Row }) => ( + + ), }, { accessorKey: "path", header: ({ column }: { column: { id: string } }) => ( ) : (

- {useTrans("back.table_not_known")} + {GetTranslation("back.table_not_known")}

); }, }, ]; +}; + +export default function AssociatedDocumentList(props: Props) { + const { documents } = props; + const { appEnv } = usePage().props as unknown as { + appEnv: AppEnv; + }; + const documentColumns = getColumns(appEnv); return ( <> @@ -104,5 +108,3 @@ function AssociatedDocumentList(props: Props) { ); } - -export default AssociatedDocumentList; diff --git a/resources/ts/back/Components/Documents/Filters.tsx b/resources/ts/back/Components/Documents/Filters.tsx index cf5d4e6d..6539a292 100644 --- a/resources/ts/back/Components/Documents/Filters.tsx +++ b/resources/ts/back/Components/Documents/Filters.tsx @@ -7,16 +7,10 @@ import EnumFilter from "@/back/Components/Filters/EnumFilter"; import SwitchFilter from "@/back/Components/Filters/SwitchFilter"; import MultiSelectRelationFilter from "@/back/Components/Filters/MultiSelectRelationFilter"; import FiltersWrapper from "@/back/Components/FiltersWrapper"; -import { - DocumentCategoryModel, - DocumentTagModel, - LaravelEnum, -} from "@/back/types/global"; +import { LaravelEnum } from "@/back/types/global"; function Filters() { const modelName = usePage().props.modelName as string; - const categories = usePage().props.categories as DocumentCategoryModel[]; - const tags = usePage().props.tags as DocumentTagModel[]; const enumDocumentStatus = usePage().props .enumDocumentStatus as LaravelEnum; diff --git a/resources/ts/back/Components/DynamicPages/DynamicInputs.tsx b/resources/ts/back/Components/DynamicPages/DynamicInputs.tsx index 89c387e7..00fa52a3 100644 --- a/resources/ts/back/Components/DynamicPages/DynamicInputs.tsx +++ b/resources/ts/back/Components/DynamicPages/DynamicInputs.tsx @@ -1,6 +1,5 @@ // * Packages import { usePage } from "@inertiajs/react"; -import { useEffect, useRef } from "react"; // * Other import SimpleString from "@/back/Components/Inputs/SimpleString"; import SimpleNumber from "@/back/Components/Inputs/SimpleNumber"; @@ -14,24 +13,23 @@ import SimpleColorPicker from "@/back/Components/Inputs/SimpleColorPicker"; import SimpleYoutubeId from "@/back/Components/Inputs/SimpleYoutubeId"; import SimpleVimeoId from "@/back/Components/Inputs/SimpleVimeoId"; import EnumSelect from "@/back/Components/Inputs/EnumSelect"; -import { ModelSelect } from "@/back/Components/Inputs/ModelSelect"; import StaticRouteSelect from "@/back/Components/Inputs/StaticRouteSelect"; +import DynamicWrapperModelSelect from "@/back/Components/DynamicPages/DynamicWrapperModelSelect"; +import { LaravelEnum } from "@/back/types/global"; import { ConfigField, DynamicFieldTypes, DynamicInternalField, - LaravelEnum, -} from "@/back/types/global"; +} from "@/back/types/dynamic-pages"; import str from "@/hooks/use-string"; import useTrans from "@/hooks/use-translations"; import { - extractExcludedIdFromExists, extractLaravelValidationValue, extractMinMaxFromValidation, extractRequiredValidation, - extractTableFromExists, } from "@/hooks/use-laravel-validation"; +/* eslint-disable no-unused-vars */ type DynamicInputsProps = { name: string; config: ConfigField[]; @@ -44,6 +42,7 @@ type DynamicInputsProps = { reset: (name?: string) => void; parentErrors?: Record | null; }; +/* eslint-enable no-unused-vars */ // Allows to get specific input from enum value. export default function DynamicInputs({ @@ -332,55 +331,14 @@ export default function DynamicInputs({ ); // Model select. case enumDynamicFieldTypes.model.value: - // Extract only the model name from the validation. - const targetModel = - extractTableFromExists(field.validation) || - "dynamic_pages"; - - // Add a custom prefix in the last part of the name. - const newName = fieldName.replace( - /([^.\s]+)$/, - "reserved_$1", - ); - - // Set a state at false. - const initializedRef = useRef(false); - - // Set the model name in the custom key only once. - useEffect(() => { - if (initializedRef.current) return; - initializedRef.current = true; - - // If the value isn't already present in content. - if ( - content && - !content[ - `reserved_${field.key}` as keyof typeof content - ] - ) { - setData(newName, targetModel); - } - }, [newName, targetModel]); - return ( - ); // Static route select. diff --git a/resources/ts/back/Components/DynamicPages/DynamicList.tsx b/resources/ts/back/Components/DynamicPages/DynamicList.tsx index e40ef243..591d7f15 100644 --- a/resources/ts/back/Components/DynamicPages/DynamicList.tsx +++ b/resources/ts/back/Components/DynamicPages/DynamicList.tsx @@ -1,6 +1,5 @@ // * Packages import { CSS } from "@dnd-kit/utilities"; -import { GripVerticalIcon, TrashIcon } from "lucide-react"; import { closestCenter, DndContext, @@ -15,8 +14,9 @@ import { useSortable, verticalListSortingStrategy, } from "@dnd-kit/sortable"; -import { useState } from "react"; import { usePage } from "@inertiajs/react"; +import { useState } from "react"; +import { GripVerticalIcon, TrashIcon } from "lucide-react"; // * Components shadcn import { Accordion, @@ -35,241 +35,17 @@ import { extractMinMaxFromValidation, extractRequiredValidation, } from "@/hooks/use-laravel-validation"; -import { ConfigField, DynamicInternalField } from "@/back/types/global"; -import { cn } from "@/lib/utils"; - -type DynamicListProps = { - name: string; - parentName: string; - value: DynamicInternalField[]; - enumLabel: string; - field: ConfigField; - content: DynamicInternalField | null; - isChildren: boolean; - setData: ( - name: string, - value: string | boolean | File | object | null, - ) => void; - reset: (name?: string) => void; -}; - -// Allows to get recursive list inputs. -export default function DynamicList({ - name, - parentName, - value, - enumLabel, - field, - content, - isChildren, - setData, - reset, -}: DynamicListProps) { - // Notice user there must be children config. - if (!field.children && field.children!.length > 0) { - return ( -

- {useTrans("back.list_children_needed", { - params: { enumLabel }, - })} -

- ); - } - - // Errors. - const { errors } = usePage().props; - const [localErrors, setLocalErrors] = - useState>(errors); - - // Drag & drop module for user interactions (mouse, touch, keyboard or other). - const sensors = useSensors(useSensor(PointerSensor)); - - // Define a local identifier for each element to enable unique identification during drag and drop. - const localValue = value.map((elmt, index) => { - return { ...elmt, id: elmt.id ?? index + 1 }; - }); - - // Order all elements depending on order values. - const sortedListItems = [...localValue].sort((a, b) => a.order - b.order); - - // Extract some values from laravel rules validation. - const isRequired = extractRequiredValidation(field.validation); - const configLimitMax = - extractMinMaxFromValidation(field.validation).max ?? 1; - - // Accordion open index. - const [openIndex, setOpenIndex] = useState(""); - - /** - * Get all elements without each unique identification values. - * @param fields DynamicInternalField[] - * @returns Omit[] - */ - const removeIds = ( - fields: DynamicInternalField[], - ): Omit[] => { - return fields.map(({ id, ...rest }) => { - const newObj: any = {}; - - for (const key in rest) { - const value = rest[key as keyof typeof rest]; - // If it is an array, we treat it recursively. - if (Array.isArray(value)) { - newObj[key] = removeIds(value); - } - // Otherwise, we keep the value as it is. - else { - newObj[key] = value; - } - } - return newObj; - }); - }; - - /** - * Remove a specific element from the list. - * @param parentName string - * @param fieldKey string - * @param index number - * @return void - */ - const handleRemoveField = ( - parentName: string, - fieldKey: string, - index: number, - ) => { - // Get the current list of elements. - const list = Array.isArray(content?.[fieldKey as keyof typeof content]) - ? // @ts-ignore - [...content[fieldKey as keyof typeof content]] - : []; - // Remove the targeted element. - list.splice(index, 1); - // Save the new list updated. - setData(`${parentName}.${fieldKey}`, list); - // Reset all errors because of alter elements list. - setLocalErrors({}); - }; - - /** - * Reorder the elements list. - * @param event DragEndEvent - * @return void - */ - const handleOnDragEnd = (event: DragEndEvent) => { - const { active, over } = event; - - // Check the unique identification value. - if (!over || active.id === over.id) return; - - // Get the two elements that are switchable by drag and drop. - const oldIndex = sortedListItems.findIndex((i) => i.id === active.id); - const newIndex = sortedListItems.findIndex((i) => i.id === over.id); - - // Reorder all elements list to update visually all elements. - const reorderedList = arrayMove( - sortedListItems, - oldIndex, - newIndex, - ).map((item, index) => ({ - ...item, - order: index + 1, - })); - - // Save the new ordered list elements. - setData(`${parentName}.${field.key}`, reorderedList); - }; - - /** - * Add a new field to the list. - * @return void - */ - const handleAddNewField = () => { - // Set new element with unique identification and order. - const newItem = Object.fromEntries( - (field.children ?? []).map((child) => [child.key, ""]), - ); - const maxOrder = - Math.max(0, ...sortedListItems.map((field) => field.order ?? 0)) + - 1; - const maxIndex = - Math.max(0, ...sortedListItems.map((field) => field.id ?? 0)) + 1; - const newIndex = value.length; - const newItemWithOrder = { ...newItem, id: maxIndex, order: maxOrder }; - - // Save the list with the new element. - setData(`${parentName}.${field.key}.${newIndex}`, newItemWithOrder); - }; - - return ( -
- - {value.length > 0 && ( - - {/* - The main provider for dnd-kit. - It manages the entire drag-and-drop system: sensors, movement, collision detection, and lifecycle callbacks (onDragStart, onDragEnd, etc.). - */} - - {/* - Defines a reorderable list area. - It registers sortable items and applies the chosen sorting strategy (vertical, horizontal, grid) to handle item repositioning. - */} - item.id)} - strategy={verticalListSortingStrategy} - > - {/* Use the recursive component for each elements. */} - {sortedListItems.map((item, itemIndex) => ( - - ))} - - - - )} - -
- ); -} +import { ConfigField, DynamicInternalField } from "@/back/types/dynamic-pages"; +import { cn, GetTranslation } from "@/lib/utils"; +/* eslint-disable no-unused-vars */ type SortableItemProps = { id: number; - item: any; + item: DynamicInternalField | null; itemIndex: number; name: string; parentName: string; - field: any; + field: ConfigField; setData: ( name: string, value: string | boolean | File | object | null, @@ -280,6 +56,7 @@ type SortableItemProps = { setParentAccordionState: (key: string) => void; localErrors: Record; }; +/* eslint-enable no-unused-vars */ // Recursive component. function SortableItem({ @@ -350,8 +127,10 @@ function SortableItem({ variant="outline" size="icon" disabled={parentAccordionState !== ""} + /* eslint-disable react/jsx-props-no-spreading */ {...listeners} {...attributes} + /* eslint-enable react/jsx-props-no-spreading */ /> {/* Define a delete button to destroy the current element. */} @@ -385,15 +164,17 @@ function SortableItem({ {/* Get specific input from the config value. */} - + {field.children?.length && ( + + )} @@ -401,3 +182,221 @@ function SortableItem({ ); } + +/* eslint-disable no-unused-vars */ +type DynamicListProps = { + name: string; + parentName: string; + value: DynamicInternalField[]; + enumLabel: string; + field: ConfigField; + content: DynamicInternalField | null; + isChildren: boolean; + setData: ( + name: string, + value: string | boolean | File | object | null, + ) => void; + reset: (name?: string) => void; +}; +/* eslint-enable no-unused-vars */ + +// Allows to get recursive list inputs. +export default function DynamicList({ + name, + parentName, + value, + enumLabel, + field, + content, + isChildren, + setData, + reset, +}: DynamicListProps) { + // Errors. + const { errors } = usePage().props; + const [localErrors, setLocalErrors] = + useState>(errors); + + // Drag & drop module for user interactions (mouse, touch, keyboard or other). + const sensors = useSensors(useSensor(PointerSensor)); + + // Define a local identifier for each element to enable unique identification during drag and drop. + const localValue = value.map((elmt, index) => { + return { ...elmt, id: elmt.id ?? index + 1 }; + }); + + // Order all elements depending on order values. + const sortedListItems = [...localValue].sort((a, b) => a.order - b.order); + + // Extract some values from laravel rules validation. + const isRequired = extractRequiredValidation(field.validation); + const configLimitMax = + extractMinMaxFromValidation(field.validation).max ?? 1; + + // Accordion open index. + const [openIndex, setOpenIndex] = useState(""); + + // Notice user there must be children config. + if (!field.children && field.children!.length > 0) { + return ( +

+ {GetTranslation("back.list_children_needed", { + params: { enumLabel }, + })} +

+ ); + } + + /** + * Remove a specific element from the list. + * @param currentParentName string + * @param fieldKey string + * @param index number + * @return void + */ + const handleRemoveField = ( + currentParentName: string, + fieldKey: string, + index: number, + ) => { + // Get the current list of elements. + const list = Array.isArray(content?.[fieldKey as keyof typeof content]) + ? // @ts-expect-error Weird error type. + [...content[fieldKey as keyof typeof content]] + : []; + // Remove the targeted element. + list.splice(index, 1); + // Save the new list updated. + setData(`${currentParentName}.${fieldKey}`, list); + // Reset all errors because of alter elements list. + setLocalErrors({}); + }; + + /** + * Reorder the elements list. + * @param event DragEndEvent + * @return void + */ + const handleOnDragEnd = (event: DragEndEvent) => { + const { active, over } = event; + + // Check the unique identification value. + if (!over || active.id === over.id) return; + + // Get the two elements that are switchable by drag and drop. + const oldIndex = sortedListItems.findIndex((i) => i.id === active.id); + const newIndex = sortedListItems.findIndex((i) => i.id === over.id); + + // Reorder all elements list to update visually all elements. + const reorderedList = arrayMove( + sortedListItems, + oldIndex, + newIndex, + ).map((item, index) => ({ + ...item, + order: index + 1, + })); + + // Save the new ordered list elements. + setData(`${parentName}.${field.key}`, reorderedList); + }; + + /** + * Add a new field to the list. + * @return void + */ + const handleAddNewField = () => { + // Set new element with unique identification and order. + const newItem = Object.fromEntries( + (field.children ?? []).map((child) => [child.key, ""]), + ); + const maxOrder = + Math.max( + 0, + ...sortedListItems.map( + (currentField) => currentField.order ?? 0, + ), + ) + 1; + const maxIndex = + Math.max( + 0, + ...sortedListItems.map((currentField) => currentField.id ?? 0), + ) + 1; + const newIndex = value.length; + const newItemWithOrder = { ...newItem, id: maxIndex, order: maxOrder }; + + // Save the list with the new element. + setData(`${parentName}.${field.key}.${newIndex}`, newItemWithOrder); + }; + + return ( +
+ + {value.length > 0 && ( + + {/* + The main provider for dnd-kit. + It manages the entire drag-and-drop system: sensors, movement, collision detection, and lifecycle callbacks (onDragStart, onDragEnd, etc.). + */} + + {/* + Defines a reorderable list area. + It registers sortable items and applies the chosen sorting strategy (vertical, horizontal, grid) to handle item repositioning. + */} + item.id)} + strategy={verticalListSortingStrategy} + > + {/* Use the recursive component for each elements. */} + {sortedListItems.map((item, itemIndex) => ( + + handleRemoveField( + currentParentName, + fieldKey, + index, + ) + } + parentAccordionState={openIndex} + setParentAccordionState={setOpenIndex} + localErrors={localErrors} + /> + ))} + + + + )} + +
+ ); +} diff --git a/resources/ts/back/Components/DynamicPages/DynamicPageDetails.tsx b/resources/ts/back/Components/DynamicPages/DynamicPageDetails.tsx index 4b40da5c..86f3ad89 100644 --- a/resources/ts/back/Components/DynamicPages/DynamicPageDetails.tsx +++ b/resources/ts/back/Components/DynamicPages/DynamicPageDetails.tsx @@ -10,8 +10,125 @@ import { // * Other import str from "@/hooks/use-string"; import useTrans from "@/hooks/use-translations"; -import { ConfigField } from "@/back/types/global"; -import { cn } from "@/lib/utils"; +import { ConfigField } from "@/back/types/dynamic-pages"; +import { cn, GetTranslation } from "@/lib/utils"; + +type ChildItem = Record; + +const RenderRows = ( + currentValue: string | number | object, + currentConfig: ConfigField[], + hasBorders: boolean = false, + path: string = "", +): ReactNode => { + const tableNotKnownTrans = str(useTrans("back.table_not_known")) + .ucFirst() + .value(); + + return currentConfig.map((field, index) => { + const hasChildren = !!field.children?.length; + const accordionKey = path + ? `${path}.${field.key}-${index}` + : `${field.key}-${index}`; + + const rowContent = (currentHasChildren: boolean = false) => { + const cellClasses = "break-words whitespace-normal"; + const placeholderClasses = + "text-sm text-muted-foreground break-words whitespace-normal"; + const fieldValue = + currentValue[field.key as keyof typeof currentValue]; + + let cellContent: ReactNode; + if (currentHasChildren) { + const count = Array.isArray(fieldValue) + ? Object.values(fieldValue).length + : 0; + cellContent = ( +

+ {GetTranslation("back.element", { + params: { count: String(count) }, + choice: count > 1 ? 2 : 1, + })} +

+ ); + } else if (fieldValue !== null && fieldValue !== undefined) { + cellContent = ( +

+ {typeof fieldValue === "boolean" + ? GetTranslation( + fieldValue ? "back.yes" : "back.no", + ) + : fieldValue} +

+ ); + } else { + cellContent = ( +

{tableNotKnownTrans}

+ ); + } + + return ( +
+

{field.label}

+ {cellContent} + {!currentHasChildren &&
} +
+ ); + }; + + return ( + + {hasChildren ? ( + + {rowContent(hasChildren)} + + ) : ( +
{rowContent()}
+ )} + + {hasChildren && + Array.isArray( + currentValue[field.key as keyof typeof currentValue], + ) && + Object.values( + currentValue[field.key as keyof typeof currentValue], + ).length > 0 && ( + +
+ {Object.values( + currentValue[ + field.key as keyof typeof currentValue + ] as ChildItem[], + ).map((childItem: ChildItem) => ( +
+ {RenderRows( + childItem, + field.children!, + false, + `${accordionKey}-${childItem.id}`, + )} +
+ ))} +
+
+ )} +
+ ); + }); +}; type DynamicPageDetailsProps = { value: string | object; @@ -28,114 +145,6 @@ export default function DynamicPageDetails({ const transValue = str(useTrans("validation.custom.value")) .ucFirst() .value(); - const tableNotKnownTrans = str(useTrans("back.table_not_known")) - .ucFirst() - .value(); - - const RenderRows = ( - value: string | number | object, - config: ConfigField[], - hasBorders: boolean = false, - path: string = "", - ): ReactNode => - config.map((field, index) => { - const hasChildren = !!field.children?.length; - const accordionKey = path - ? `${path}.${field.key}-${index}` - : `${field.key}-${index}`; - - const rowContent = (hasChildren: boolean = false) => { - const cellClasses = "break-words whitespace-normal"; - const placeholderClasses = - "text-sm text-muted-foreground break-words whitespace-normal"; - - const fieldValue = value[field.key as keyof typeof value]; - - const cellContent = hasChildren ? ( - (() => { - const count = Array.isArray(fieldValue) - ? Object.values(fieldValue).length - : 0; - return ( -

- {useTrans("back.element", { - params: { count: String(count) }, - choice: count > 1 ? 2 : 1, - })} -

- ); - })() - ) : fieldValue !== null && fieldValue !== undefined ? ( -

- {typeof fieldValue === "boolean" - ? useTrans(fieldValue ? "back.yes" : "back.no") - : fieldValue} -

- ) : ( -

{tableNotKnownTrans}

- ); - - return ( -
-

{field.label}

- {cellContent} - {!hasChildren &&
} -
- ); - }; - - return ( - - {hasChildren ? ( - - {rowContent(hasChildren)} - - ) : ( -
{rowContent()}
- )} - - {hasChildren && - Array.isArray(value[field.key as keyof typeof value]) && - Object.values(value[field.key as keyof typeof value]) - .length > 0 && ( - -
- {Object.values( - value[field.key as keyof typeof value], - ).map( - ( - childItem: any, - childIndex: number, - ) => ( -
- {RenderRows( - childItem, - field.children!, - false, - `${accordionKey}-${childIndex}`, - )} -
- ), - )} -
-
- )} -
- ); - }); return ( diff --git a/resources/ts/back/Components/DynamicPages/DynamicWrapperModelSelect.tsx b/resources/ts/back/Components/DynamicPages/DynamicWrapperModelSelect.tsx new file mode 100644 index 00000000..79a86eec --- /dev/null +++ b/resources/ts/back/Components/DynamicPages/DynamicWrapperModelSelect.tsx @@ -0,0 +1,74 @@ +// * Packages +import { useEffect, useRef } from "react"; +// * Other +import { ModelSelect } from "@/back/Components/Inputs/ModelSelect"; +import { + extractExcludedIdFromExists, + extractRequiredValidation, + extractTableFromExists, +} from "@/hooks/use-laravel-validation"; +import { ConfigField, DynamicInternalField } from "@/back/types/dynamic-pages"; + +/* eslint-disable no-unused-vars */ +type DynamicWrapperModelSelectProps = { + field: ConfigField; + fieldName: string; + fieldValue: string | number | boolean | File | Date; + content: DynamicInternalField | null; + setData: ( + name: string, + value: string | number | boolean | File | object | null, + ) => void; + getTrans: (fieldLabel: string) => string; +}; +/* eslint-enable no-unused-vars */ + +export default function DynamicWrapperModelSelect({ + field, + fieldName, + fieldValue, + content, + setData, + getTrans, +}: DynamicWrapperModelSelectProps) { + // Extract only the model name from the validation. + const targetModel = + extractTableFromExists(field.validation) || "dynamic_pages"; + + // Add a custom prefix in the last part of the name. + const newName = fieldName.replace(/([^.\s]+)$/, "reserved_$1"); + + // Set a state at false. + const initializedRef = useRef(false); + + // Set the model name in the custom key only once. + useEffect(() => { + if (initializedRef.current) return; + initializedRef.current = true; + + // If the value isn't already present in content. + if ( + content && + !content[`reserved_${field.key}` as keyof typeof content] + ) { + setData(newName, targetModel); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [newName, targetModel]); + + return ( + + ); +} diff --git a/resources/ts/back/Components/GoToFrontView.tsx b/resources/ts/back/Components/GoToFrontView.tsx index 30cb0f0c..be0c1605 100644 --- a/resources/ts/back/Components/GoToFrontView.tsx +++ b/resources/ts/back/Components/GoToFrontView.tsx @@ -11,7 +11,7 @@ import { import { AppEnv } from "@/back/types/global"; import useTrans from "@/hooks/use-translations"; import str from "@/hooks/use-string"; -import { cn } from "@/lib/utils"; +import { cn, GetTranslation } from "@/lib/utils"; interface GoToFrontViewProps { /** Model Name used to translate the helper */ @@ -51,8 +51,13 @@ export default function GoToFrontView({ const cleanUrl = url.endsWith("/") ? url.slice(0, -1) : url; + let localePath = ""; + if (addLocale && locale !== "fr") { + localePath = `/${locale}`; + } + const definitivePath = [ - cleanUrl + (addLocale ? (locale === "fr" ? "" : `/${locale}`) : ""), + cleanUrl + localePath, additionalGroupPath, pathField, ] @@ -66,7 +71,7 @@ export default function GoToFrontView({ {isAvailable ? ( <> {str( - useTrans("back.access_to_front_page", { + GetTranslation("back.access_to_front_page", { params: { model: modelName, }, @@ -95,11 +100,14 @@ export default function GoToFrontView({ ) : ( <> {str( - useTrans("back.access_to_front_page_when_published", { - params: { - model: modelName, + GetTranslation( + "back.access_to_front_page_when_published", + { + params: { + model: modelName, + }, }, - }), + ), ) .ucFirst() .value()} diff --git a/resources/ts/back/Components/InputError.tsx b/resources/ts/back/Components/InputError.tsx index b086961b..d32233d6 100644 --- a/resources/ts/back/Components/InputError.tsx +++ b/resources/ts/back/Components/InputError.tsx @@ -31,8 +31,8 @@ export default function InputError({ {all && {errorsTrans}} - {messages.map((errorMessage, errorMessageIndex) => { - return

{errorMessage}

; + {messages.map((errorMessage) => { + return

{errorMessage}

; })}
diff --git a/resources/ts/back/Components/Inputs/CompleteInputFile.tsx b/resources/ts/back/Components/Inputs/CompleteInputFile.tsx index c356731c..c9d1bd52 100644 --- a/resources/ts/back/Components/Inputs/CompleteInputFile.tsx +++ b/resources/ts/back/Components/Inputs/CompleteInputFile.tsx @@ -14,8 +14,10 @@ import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithT import useTrans from "@/hooks/use-translations"; import str from "@/hooks/use-string"; import { useToast } from "@/hooks/use-toast"; -import { AppEnv, DynamicFieldTypes, FileValidation } from "@/back/types/global"; -import { cn } from "@/lib/utils"; +import { AppEnv } from "@/back/types/global"; +import { DynamicFieldTypes } from "@/back/types/dynamic-pages"; +import { FileValidation } from "@/back/types/others"; +import { cn, GetTranslation } from "@/lib/utils"; type FileWithPreview = FileWithPath & { preview: string; @@ -59,23 +61,35 @@ export default function CompleteInputFile(props: CompleteInputFileProps) { fileName = "image", hasRefreshBtn = true, hasFile, + parentErrors, } = props; const { toast } = useToast(); const { errors } = usePage().props; const { url } = usePage().props.appEnv as AppEnv; const [isReset, setIsReset] = useState(false); + const [isDialogOpen, setDialogOpen] = useState(false); + const [accept, setAccept] = useState>>({ + "image/*": [], + }); + const [extensions, setExtensions] = useState(); + const finalFileName = `${fileName}.png`; - const transValueIntoFile = (localData: string | undefined | null) => { - if (localData) { - const fileName = getFileNameFromPath(localData); - return { - preview: `${url}/${localData}`, - name: fileName ?? "", - } as FileWithPreview; - } - return null; + const isRefreshDisabled = value != null && typeof value === "string"; + const localErrors = parentErrors ?? errors; + + const localValidationRules = { + width: validationRules?.width + ? parseInt(validationRules.width, 10) + : 250, + height: validationRules?.height + ? parseInt(validationRules?.height, 10) + : 250, }; + const localMaxFileSize = maxFileSize + ? parseInt(maxFileSize, 10) * 1024 + : 50000; + /** * Get the fileName from the path given by the database value * @param path string|null @@ -89,7 +103,16 @@ export default function CompleteInputFile(props: CompleteInputFileProps) { return last || null; }; - const isRefreshDisabled = value != null && typeof value === "string"; + const transValueIntoFile = (localData: string | undefined | null) => { + if (localData) { + const currentFileName = getFileNameFromPath(localData); + return { + preview: `${url}/${localData}`, + name: currentFileName ?? "", + } as FileWithPreview; + } + return null; + }; const [selectedFile, setSelectedFile] = useState( () => { @@ -105,46 +128,6 @@ export default function CompleteInputFile(props: CompleteInputFileProps) { }, ); - const localErrors = props.parentErrors ?? errors; - - const [isDialogOpen, setDialogOpen] = useState(false); - const [accept, setAccept] = useState>>({ - "image/*": [], - }); - const [extensions, setExtensions] = useState(); - - const localValidationRules = { - width: validationRules?.width - ? parseInt(validationRules.width, 10) - : 250, - height: validationRules?.height - ? parseInt(validationRules?.height, 10) - : 250, - }; - - const localMaxFileSize = maxFileSize - ? parseInt(maxFileSize, 10) * 1024 - : 50000; - - // Translations - const transSelectImgZone = useTrans("back.image_cropper.select_img_zone"); - const transValidationAllFormats = str( - useTrans("back.image_cropper.validation.all_formats"), - ) - .ucFirst() - .value(); - const getTransValidationAllowedFormats = (extensions: string) => { - return str( - useTrans("back.image_cropper.validation.allowed_formats", { - params: { - formats: extensions, - }, - }), - ) - .ucFirst() - .value(); - }; - // Fonction utilitaire pour formater la taille de fichier const formatFileSize = (bytes: number): string => { if (bytes === 0) return "0 Bytes"; @@ -156,6 +139,38 @@ export default function CompleteInputFile(props: CompleteInputFileProps) { return `${currentBytes} ${currentSize}`; }; + // Translations + const transSelectImgZone = useTrans("back.image_cropper.select_img_zone"); + const transValidationAllFormats = str( + useTrans("back.image_cropper.validation.all_formats"), + ) + .ucFirst() + .value(); + const transCropperFileUploaded = useTrans( + "back.image_cropper.state_file_uploaded", + ); + const transCancel = useTrans("back.cancel"); + const transRulesWeight = useTrans( + "back.image_cropper.validation.rules_weight", + { + params: { + size: formatFileSize(localMaxFileSize).toString(), + }, + }, + ); + const transRefresh = useTrans("back.refresh_original"); + const getTransValidationAllowedFormats = (targetExtensions: string) => { + return str( + GetTranslation("back.image_cropper.validation.allowed_formats", { + params: { + formats: targetExtensions, + }, + }), + ) + .ucFirst() + .value(); + }; + const buildDropzoneAccept = (): Record => { if (!allowedFormats) return {}; @@ -322,11 +337,7 @@ export default function CompleteInputFile(props: CompleteInputFileProps) {

- {str( - useTrans( - "back.image_cropper.state_file_uploaded", - ), - ) + {str(transCropperFileUploaded) .ucFirst() .value()}

@@ -342,7 +353,7 @@ export default function CompleteInputFile(props: CompleteInputFileProps) { setSelectedFile(null); setData(name, null); }} - tooltipContent={str(useTrans("back.cancel")) + tooltipContent={str(transCancel) .ucFirst() .value()} /> @@ -371,21 +382,7 @@ export default function CompleteInputFile(props: CompleteInputFileProps) { extensions, ) : transValidationAllFormats} - .{" "} - {str( - useTrans( - "back.image_cropper.validation.rules_weight", - { - params: { - size: formatFileSize( - localMaxFileSize, - ).toString(), - }, - }, - ), - ) - .ucFirst() - .value()} + . {str(transRulesWeight).ucFirst().value()}
@@ -405,9 +402,7 @@ export default function CompleteInputFile(props: CompleteInputFileProps) { } }} content={} - tooltipContent={str(useTrans("back.refresh_original")) - .ucFirst() - .value()} + tooltipContent={str(transRefresh).ucFirst().value()} /> )}
diff --git a/resources/ts/back/Components/Inputs/EnumSelect.tsx b/resources/ts/back/Components/Inputs/EnumSelect.tsx index fa2121b2..c51f98b9 100644 --- a/resources/ts/back/Components/Inputs/EnumSelect.tsx +++ b/resources/ts/back/Components/Inputs/EnumSelect.tsx @@ -12,7 +12,7 @@ import { import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; // * Other -import BtnOptionnalLinkWithTooltip from "@/back/Components//BtnOptionnalLinkWithTooltip"; +import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; import InputError from "@/back/Components/InputError"; import { AppEnv } from "@/back/types/global"; import { cn } from "@/lib/utils"; @@ -29,7 +29,9 @@ type EnumSelectProps = { required?: boolean; disabled?: boolean; parentErrors?: Record | null; + // eslint-disable-next-line no-unused-vars setData: (name: string, value: number | null) => void; + // eslint-disable-next-line no-unused-vars onSelectValue?: (value: number | null) => void; }; @@ -53,7 +55,7 @@ export default function EnumSelect({ // List of options. const initalOptions = Object.fromEntries( Object.entries(options).filter( - ([key, item]) => !exclude.includes(item.value), + ([, item]) => !exclude.includes(item.value), ), ); const [currentOptions, setCurrentOptions] = @@ -74,6 +76,9 @@ export default function EnumSelect({ // Component states. const [open, setOpen] = useState(false); + const transNoResults = useTrans("back.table_no_results"); + const transDelete = useTrans("crud.actions.delete"); + /** * Get the identification field of specific option. * @param option Model @@ -130,13 +135,14 @@ export default function EnumSelect({ } const optionsFiltered = Object.fromEntries( - Object.entries(initalOptions).filter(([key, item]) => + Object.entries(initalOptions).filter(([, item]) => item.label.toLowerCase().includes(search), ), ); // Get updated options list with search. setCurrentOptions(optionsFiltered); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [search, options]); return ( @@ -201,7 +207,7 @@ export default function EnumSelect({ > {Object.values(currentOptions).length === 0 && (

- {useTrans("back.table_no_results")} + {transNoResults}

)}
@@ -229,11 +235,7 @@ export default function EnumSelect({ } tooltipContent={ -

- {str(useTrans("crud.actions.delete")) - .ucFirst() - .value()} -

+

{str(transDelete).ucFirst().value()}

} type="button" variant="destructive" diff --git a/resources/ts/back/Components/Inputs/ImageCropper.tsx b/resources/ts/back/Components/Inputs/ImageCropper.tsx index 3d01ac97..aa9c7aee 100644 --- a/resources/ts/back/Components/Inputs/ImageCropper.tsx +++ b/resources/ts/back/Components/Inputs/ImageCropper.tsx @@ -49,10 +49,10 @@ import { TooltipTrigger, } from "@/components/ui/tooltip"; // * Other -import useTrans, { UseTransOptions } from "@/hooks/use-translations"; +import useTrans from "@/hooks/use-translations"; import str from "@/hooks/use-string"; import useDeviceType from "@/hooks/use-device-type"; -import { cn } from "@/lib/utils"; +import { cn, GetTranslation } from "@/lib/utils"; interface CropValidationRules { width: number; @@ -166,13 +166,8 @@ export default function ImageCropper({ const deltaX = cropCenterX - imageCenterX; const deltaY = cropCenterY - imageCenterY; - // Calculer le ratio de qualité : on veut travailler à la résolution originale - // Le ratio est : taille originale / taille affichée - const qualityRatio = 1 / displayScale; - // Taille du canvas temporaire basée sur la résolution originale de la zone croppée // On limite toutefois pour éviter des canvas trop grands (max 4x la taille finale) - const maxRatio = Math.min(qualityRatio, 4); const tmpWidth = Math.ceil(crop.width / displayScale); const tmpHeight = Math.ceil(crop.height / displayScale); @@ -534,10 +529,6 @@ export default function ImageCropper({ .ucFirst() .value(); - const GetTranslation = (keyName: string, options?: UseTransOptions) => { - return useTrans(keyName, options); - }; - // Filters (brightness, contrast, saturation, sepia, grayscale). const imageFilterOptions = [ { diff --git a/resources/ts/back/Components/Inputs/ModelSelect.tsx b/resources/ts/back/Components/Inputs/ModelSelect.tsx index 66b42ea6..09c852a4 100644 --- a/resources/ts/back/Components/Inputs/ModelSelect.tsx +++ b/resources/ts/back/Components/Inputs/ModelSelect.tsx @@ -24,6 +24,8 @@ import { ChevronLeftIcon, ChevronRightIcon, } from "lucide-react"; +import axios, { isAxiosError } from "axios"; +import { route } from "ziggy-js"; // * Components shadcn import { Separator } from "@/components/ui/separator"; import { Button } from "@/components/ui/button"; @@ -42,21 +44,14 @@ import { } from "@/components/ui/command"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; +import { Skeleton } from "@/components/ui/skeleton"; // * Other import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; import InputError from "@/back/Components/InputError"; -import useTrans from "@/hooks/use-translations"; import str from "@/hooks/use-string"; -import { cn } from "@/lib/utils"; -import axios from "axios"; -import { route } from "ziggy-js"; -import { - AppEnv, - Model, - Paginator, - TranslationField, -} from "@/back/types/global"; -import { Skeleton } from "@/components/ui/skeleton"; +import { cn, GetTranslation } from "@/lib/utils"; +import { AppEnv, DefaultModel, Paginator } from "@/back/types/global"; +import { TranslationField } from "@/back/types/i18n"; /** * Animation types and configurations @@ -115,13 +110,15 @@ interface ModelSelectProps * Callback function triggered when the selected values change. * Receives an array of the new selected values. */ + // eslint-disable-next-line no-unused-vars onValueChange?: (value: number[]) => void; /** * Callback function triggered when the selected values change. * Receives a single or array of selected Model(s). */ - onModelChange?: (value: Model[]) => void; + // eslint-disable-next-line no-unused-vars + onModelChange?: (value: DefaultModel[]) => void; /** The default selected values when the component mounts. */ defaultValues?: number[]; @@ -161,6 +158,7 @@ interface ModelSelectProps * If true, renders the multi-select component as a child of another component. * Optional, defaults to false. */ + // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars asChild?: boolean; /** @@ -294,6 +292,7 @@ interface ModelSelectProps /** * The fonction to set the data in useForm inertia */ + // eslint-disable-next-line no-unused-vars setData: (name: string, value: number[] | number | null) => void; /** @@ -357,6 +356,7 @@ export interface ModelSelectRef { /** * Set selected values programmatically */ + // eslint-disable-next-line no-unused-vars setSelectedValues: (values: number[]) => void; /** * Clear all selected values @@ -388,6 +388,7 @@ export const ModelSelect = forwardRef( animationConfig, maxCount = 3, modalPopover = false, + // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars asChild = false, className, hideSelectAll = false, @@ -415,7 +416,9 @@ export const ModelSelect = forwardRef( const [selectedValues, setSelectedValues] = useState(defaultValues); - const [selectedModels, setSelectedModels] = useState([]); + const [selectedModels, setSelectedModels] = useState( + [], + ); const [isPopoverOpen, setIsPopoverOpen] = useState(false); const [isAnimating, setIsAnimating] = useState(false); @@ -445,8 +448,8 @@ export const ModelSelect = forwardRef( const [loading, setLoading] = useState(false); const [initialLoading, setInitialLoading] = useState(true); - const [options, setOptions] = useState([]); - const [optionsMerged, setOptionsMerged] = useState([]); + const [options, setOptions] = useState([]); + const [optionsMerged, setOptionsMerged] = useState([]); // Custom by field parameter or predefined values (title, name or label). const [dynamicField, setDynamicField] = useState( field ?? null, @@ -489,16 +492,6 @@ export const ModelSelect = forwardRef( return response.data; }; - /** - * Get specific page. - * @param pageNumber number - * @return void - */ - const goToPage = async (pageNumber: number) => { - if (pageNumber < 1 || pageNumber > lastPage) return; - await getData(pageNumber); - }; - /** * Get new options list with his target field. * @param pageNumber number @@ -506,27 +499,30 @@ export const ModelSelect = forwardRef( */ const getData = async (pageNumber = 1) => { // Increment search id. - const requestId = ++requestIdRef.current; + requestIdRef.current += 1; + const requestId = requestIdRef.current; setLoading(true); try { const data = (await fetchOptions(pageNumber)) as { items: Paginator; field: string; - model: Model[]; + model: DefaultModel[]; }; // Ignore obsolete answers. if (requestId !== requestIdRef.current) return; - const dataModel: Model[] = data.model + const dataModel: DefaultModel[] = data.model ? Object.values(data.model) : []; - const dataItems: Model[] = Array.isArray(data?.items?.data) + const dataItems: DefaultModel[] = Array.isArray( + data?.items?.data, + ) ? data.items.data : []; setOptionsMerged(() => { const merged = [...dataModel, ...dataItems] // Supprime les éléments undefined ou sans id .filter( - (item): item is Model => + (item): item is DefaultModel => !!item && item.id !== undefined, ) // Supprime les doublons par id @@ -542,11 +538,14 @@ export const ModelSelect = forwardRef( setOptions(data.items.data ?? []); setPage(data.items.current_page); setLastPage(data.items.last_page ?? 1); - } catch (err: any) { + } catch (err: unknown) { // Ignore obsolete errors. if (requestId !== requestIdRef.current) return; console.error(err); - setErrorsAjax(err.response?.data?.errors); + if (isAxiosError(err) && err.response?.data?.errors) { + setErrorsAjax(err.response?.data?.errors); + } + setOptions([]); setLastPage(1); } finally { @@ -557,23 +556,42 @@ export const ModelSelect = forwardRef( setInitialLoading(false); } }; + + /** + * Get specific page. + * @param pageNumber number + * @return void + */ + const goToPage = async (pageNumber: number) => { + if (pageNumber < 1 || pageNumber > lastPage) return; + await getData(pageNumber); + }; + useEffect(() => { if (disabled) { - return; + return undefined; } // Reset options list if empty search or null if (targetModel && searchValue.length === 0) { getData(); - return; + return undefined; } // Nothing to do if search is below 3 caracters. - if (searchValue.length < 3) return; + if (searchValue.length < 3) return undefined; // Get updated options list with search. const handler = setTimeout(() => getData(), 300); - return () => clearTimeout(handler); + + function cleanup(): void { + if (handler) { + clearTimeout(handler); + } + } + + return cleanup; + // eslint-disable-next-line react-hooks/exhaustive-deps }, [searchValue, targetModel]); const announce = useCallback( @@ -595,15 +613,19 @@ export const ModelSelect = forwardRef( const selectedCountId = `${multiSelectId}-count`; // Translations. - const transDelete = useTrans("crud.actions.delete"); - const transClose = useTrans("back.close"); - const transClear = useTrans("back.clear"); - const transNoResults = useTrans("back.table_no_results"); - const transSearchOptions = useTrans("back.multi_select.search_options"); - const transSelectAll = useTrans("back.multi_select.select_all"); - const transMore = useTrans("back.multi_select.more"); - const transClosePopover = useTrans("back.multi_select.close_popover"); - const transClearList = useTrans("back.multi_select.clear_list"); + const transDelete = GetTranslation("crud.actions.delete"); + const transClose = GetTranslation("back.close"); + const transClear = GetTranslation("back.clear"); + const transNoResults = GetTranslation("back.table_no_results"); + const transSearchOptions = GetTranslation( + "back.multi_select.search_options", + ); + const transSelectAll = GetTranslation("back.multi_select.select_all"); + const transMore = GetTranslation("back.multi_select.more"); + const transClosePopover = GetTranslation( + "back.multi_select.close_popover", + ); + const transClearList = GetTranslation("back.multi_select.clear_list"); const prevDefaultValueRef = useRef(defaultValues); @@ -622,6 +644,7 @@ export const ModelSelect = forwardRef( onValueChange(defaultValues); } setData(name, multiple ? defaultValues : defaultValues[0]); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [defaultValues, onValueChange]); const buttonRef = useRef(null); @@ -665,6 +688,7 @@ export const ModelSelect = forwardRef( } }, }), + // eslint-disable-next-line react-hooks/exhaustive-deps [resetToDefault, selectedValues, onValueChange], ); @@ -673,7 +697,7 @@ export const ModelSelect = forwardRef( >("desktop"); useEffect(() => { - if (typeof window === "undefined") return; + if (typeof window === "undefined") return undefined; const handleResize = () => { const width = window.innerWidth; if (width < 640) { @@ -780,13 +804,12 @@ export const ModelSelect = forwardRef( return ""; }; - const getAllOptions = useCallback((): Model[] => { + const getAllOptions = useCallback((): DefaultModel[] => { if (optionsMerged.length === 0) return []; - let allOptions: Model[]; - allOptions = optionsMerged; + const allOptions: DefaultModel[] = optionsMerged; const valueSet = new Set(); const duplicates: string[] = []; - const uniqueOptions: Model[] = []; + const uniqueOptions: DefaultModel[] = []; allOptions.forEach((option) => { if (valueSet.has(option.id.toString())) { duplicates.push(option.id.toString()); @@ -817,13 +840,11 @@ export const ModelSelect = forwardRef( ); } return deduplicateOptions ? uniqueOptions : allOptions; - }, [options, deduplicateOptions]); + }, [optionsMerged, deduplicateOptions]); const getOptionByValue = useCallback( - (id: number): Model | undefined => { - const option = getAllOptions().find( - (option) => option.id === id, - ); + (id: number): DefaultModel | undefined => { + const option = getAllOptions().find((opt) => opt.id === id); if (!option && process.env.NODE_ENV === "development") { console.warn( `MultiSelect: Option with value "${id}" not found in options list`, @@ -860,7 +881,7 @@ export const ModelSelect = forwardRef( } }; - const toggleOption = (option: Model) => { + const toggleOption = (option: DefaultModel) => { if (disabled) return; // MODE SINGLE SELECT if (!multiple) { @@ -1067,73 +1088,77 @@ export const ModelSelect = forwardRef( } }, [isPopoverOpen]); - useEffect(() => { - const selectedCount = selectedValues.length; - const allOptions = getAllOptions(); - const totalOptions = allOptions.length; - if (selectedCount !== prevSelectedCount.current) { - const diff = selectedCount - prevSelectedCount.current; - if (diff > 0) { - const addedItems = selectedValues.slice(-diff); - const addedLabels = addedItems - .map( - (value) => - allOptions.find((opt) => opt.id === value)?.[ - dynamicField as keyof Model - ], - ) - .filter(Boolean); + useEffect( + () => { + const selectedCount = selectedValues.length; + const allOptions = getAllOptions(); + const totalOptions = allOptions.length; + if (selectedCount !== prevSelectedCount.current) { + const diff = selectedCount - prevSelectedCount.current; + if (diff > 0) { + const addedItems = selectedValues.slice(-diff); + const addedLabels = addedItems + .map( + (value) => + allOptions.find( + (opt) => opt.id === value, + )?.[dynamicField as keyof DefaultModel], + ) + .filter(Boolean); - if (addedLabels.length === 1) { + if (addedLabels.length === 1) { + announce( + `${addedLabels[0]} selected. ${selectedCount} of ${totalOptions} options selected.`, + ); + } else { + announce( + `${addedLabels.length} options selected. ${selectedCount} of ${totalOptions} total selected.`, + ); + } + } else if (diff < 0) { announce( - `${addedLabels[0]} selected. ${selectedCount} of ${totalOptions} options selected.`, - ); - } else { - announce( - `${addedLabels.length} options selected. ${selectedCount} of ${totalOptions} total selected.`, + `Option removed. ${selectedCount} of ${totalOptions} options selected.`, ); } - } else if (diff < 0) { - announce( - `Option removed. ${selectedCount} of ${totalOptions} options selected.`, - ); + prevSelectedCount.current = selectedCount; } - prevSelectedCount.current = selectedCount; - } - if (isPopoverOpen !== prevIsOpen.current) { - if (isPopoverOpen) { - announce( - `Dropdown opened. ${totalOptions} options available. Use arrow keys to navigate.`, - ); - } else { - announce("Dropdown closed."); + if (isPopoverOpen !== prevIsOpen.current) { + if (isPopoverOpen) { + announce( + `Dropdown opened. ${totalOptions} options available. Use arrow keys to navigate.`, + ); + } else { + announce("Dropdown closed."); + } + prevIsOpen.current = isPopoverOpen; } - prevIsOpen.current = isPopoverOpen; - } - if ( - searchValue !== prevSearchValue.current && - searchValue !== undefined - ) { - if (searchValue && isPopoverOpen) { - const filteredCount = allOptions.length; + if ( + searchValue !== prevSearchValue.current && + searchValue !== undefined + ) { + if (searchValue && isPopoverOpen) { + const filteredCount = allOptions.length; - announce( - `${filteredCount} option${ - filteredCount === 1 ? "" : "s" - } found for "${searchValue}"`, - ); + announce( + `${filteredCount} option${ + filteredCount === 1 ? "" : "s" + } found for "${searchValue}"`, + ); + } + prevSearchValue.current = searchValue; } - prevSearchValue.current = searchValue; - } - }, [ - selectedValues, - isPopoverOpen, - searchValue, - announce, - getAllOptions, - ]); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [ + selectedValues, + isPopoverOpen, + searchValue, + announce, + getAllOptions, + ], + ); useEffect(() => { if ( @@ -1161,10 +1186,10 @@ export const ModelSelect = forwardRef( } }, [minSelection, maxSelection, multiple]); - const getModelField = (option: Model): string => { - const value = option[dynamicField as keyof Model] as unknown as - | TranslationField - | string; + const getModelField = (option: DefaultModel): string => { + const value = option[ + dynamicField as keyof DefaultModel + ] as unknown as TranslationField | string; if (typeof value === "object") return String(value[locale] ?? ""); return String(value ?? ""); }; @@ -1209,7 +1234,7 @@ export const ModelSelect = forwardRef( } selected: ${selectedValues .map((value) => { const option = getOptionByValue(value); - if (!option) return; + if (!option) return undefined; return getModelField(option); }) .filter(Boolean) @@ -1218,6 +1243,7 @@ export const ModelSelect = forwardRef( - )} - -
-
- )} - - ) : ( + {selectedValues.length === 0 && (

{placeholder} @@ -1473,6 +1281,215 @@ export const ModelSelect = forwardRef(

)} + {selectedValues.length > 0 && + (initialLoading ? ( + + ) : ( +
+
+ {selectedValues + .slice( + 0, + responsiveSettings.maxCount, + ) + .map((value) => { + const option = + getOptionByValue(value); + if (!option) { + return null; + } + const badgeStyle: CSSProperties = + { + animationDuration: `${animation}s`, + }; + return multiple ? ( + svg]:pointer-events-auto", + )} + style={{ + ...badgeStyle, + animationDuration: `${ + animationConfig?.duration || + animation + }s`, + animationDelay: `${animationConfig?.delay || 0}s`, + }} + > + + {getModelField( + option, + )} + +
{ + event.stopPropagation(); + toggleOption( + option, + ); + }} + onKeyDown={( + event, + ) => { + if ( + event.key === + "Enter" || + event.key === + " " + ) { + event.preventDefault(); + event.stopPropagation(); + toggleOption( + option, + ); + } + }} + aria-label={`Remove ${option[dynamicField as keyof DefaultModel]} from selection`} + className="ml-2 h-4 w-4 cursor-pointer hover:bg-white/20 rounded-sm -m-0.5 focus:outline-none focus:ring-1 focus:ring-white/50" + > + +
+
+ ) : ( +

+ + {getModelField( + option, + )} + +

+ ); + }) + .filter(Boolean)} + {selectedValues.length > + responsiveSettings.maxCount && ( + svg]:pointer-events-auto", + )} + style={{ + animationDuration: `${animationConfig?.duration || animation}s`, + animationDelay: `${animationConfig?.delay || 0}s`, + }} + > + {`+ ${ + selectedValues.length - + responsiveSettings.maxCount + } ${transMore}`} + { + event.stopPropagation(); + clearExtraOptions(); + }} + /> + + )} +
+
+ {!required && ( + + )} + +
+
+ ))} ( content={ <> - {useTrans( + {GetTranslation( "pagination.previous", )} } tooltipContent={

- {useTrans( + {GetTranslation( "pagination.specific_page", { params: { @@ -1692,13 +1709,15 @@ export const ModelSelect = forwardRef( - {useTrans("pagination.next")} + {GetTranslation( + "pagination.next", + )} } tooltipContent={

- {useTrans( + {GetTranslation( "pagination.specific_page", { params: { diff --git a/resources/ts/back/Components/Inputs/MultiSelect.tsx b/resources/ts/back/Components/Inputs/MultiSelect.tsx index 4989f8f2..64636e32 100644 --- a/resources/ts/back/Components/Inputs/MultiSelect.tsx +++ b/resources/ts/back/Components/Inputs/MultiSelect.tsx @@ -142,6 +142,7 @@ interface MultiSelectProps * Callback function triggered when the selected values change. * Receives an array of the new selected values. */ + // eslint-disable-next-line no-unused-vars onValueChange?: (value: string[]) => void; /** The default selected values when the component mounts. */ @@ -182,6 +183,7 @@ interface MultiSelectProps * If true, renders the multi-select component as a child of another component. * Optional, defaults to false. */ + // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars asChild?: boolean; /** @@ -315,6 +317,7 @@ interface MultiSelectProps /** * The fonction to set the data in useForm inertia */ + // eslint-disable-next-line no-unused-vars setData: (name: string, value: string[]) => void; /** @@ -362,6 +365,7 @@ export interface MultiSelectRef { /** * Set selected values programmatically */ + // eslint-disable-next-line no-unused-vars setSelectedValues: (values: string[]) => void; /** * Clear all selected values @@ -393,6 +397,7 @@ export const MultiSelect = forwardRef( animationConfig, maxCount = 3, modalPopover = false, + // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars asChild = false, className, hideSelectAll = false, @@ -482,6 +487,7 @@ export const MultiSelect = forwardRef( onValueChange(defaultValue); } setData(name, defaultValue); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [defaultValue, onValueChange]); const buttonRef = useRef(null); @@ -525,6 +531,7 @@ export const MultiSelect = forwardRef( } }, }), + // eslint-disable-next-line react-hooks/exhaustive-deps [resetToDefault, selectedValues, onValueChange], ); @@ -533,7 +540,7 @@ export const MultiSelect = forwardRef( >("desktop"); useEffect(() => { - if (typeof window === "undefined") return; + if (typeof window === "undefined") return undefined; const handleResize = () => { const width = window.innerWidth; if (width < 640) { @@ -686,7 +693,7 @@ export const MultiSelect = forwardRef( const getOptionByValue = useCallback( (value: string): MultiSelectOption | undefined => { const option = getAllOptions().find( - (option) => option.value === value, + (opt) => opt.value === value, ); if (!option && process.env.NODE_ENV === "development") { console.warn( @@ -1053,6 +1060,7 @@ export const MultiSelect = forwardRef(

- {useTrans("back.mods")} + {GetTranslation("back.mods")}

{mods.map((mod) => { @@ -115,7 +113,7 @@ export default function AppSettings({ className = "" }: AppSettingsProps) { <>

- {useTrans("back.languages")} + {GetTranslation("back.languages")}

ReactNode; }>; }>; type CustomDropdownMenuItemProps = { /** Link of the action. */ - route: string; + targetRoute: string; /** Lucide icon of the action. */ - icon?: ElementType; + icon: ElementType; /** Translation of the action. */ - trans?: string; + trans: string; }; +/** + * Sidenav action button. + * + * @param string targetRoute + * @param ElementType icon + * @param string trans + * @return ReactNode + */ +function CustomDropdownMenuItem({ + targetRoute, + icon: Icon, + trans, +}: CustomDropdownMenuItemProps): ReactNode { + return ( + + + + ); +} + export default function AppSidebar() { const { appEnv, auth, enumUserRole } = usePage().props as unknown as { appEnv: AppEnv; @@ -102,29 +128,103 @@ export default function AppSidebar() { const { state } = useSidebar(); const prevStateRef = useRef(state); const isCollapsed = state === "collapsed"; + + // Translations. + const transDefaultSideAction = str( + useTrans(`crud.actions.${Views.CREATION.label}`), + ) + .ucFirst() + .value(); + const transPages = useTrans("back.pages"); + const transDashboard = useTrans("back.dashboard"); + const transWebsite = useTrans("back.your_website"); + const transDynamicPages = useTrans("models.classes.dynamic-page", { + choice: 2, + }); + const transModules = useTrans("back.modules"); + const transNews = useTrans("models.classes.news", { choice: 2 }); + const transNewsCategories = useTrans("models.classes.news-category", { + choice: 2, + }); + const transNewsTags = useTrans("models.classes.news-tag", { choice: 2 }); + const transContactCount = useTrans("models.classes.contact-count"); + const transDocuments = useTrans("models.classes.document", { choice: 2 }); + const transDocumentCategories = useTrans( + "models.classes.document-category", + { choice: 2 }, + ); + const transDocumentTags = useTrans("models.classes.document-tag", { + choice: 2, + }); + const transCompanies = useTrans("models.classes.company", { choice: 2 }); + const transCompanyActivities = useTrans("models.classes.company-activity", { + choice: 2, + }); + const transCompanySectors = useTrans("models.classes.company-sector", { + choice: 2, + }); + const transAdmin = useTrans("enums.user-role.admin"); + const transUsers = useTrans("models.classes.user"); + const transActivityLogs = useTrans("models.classes.activity-log"); + const transPageConfigs = useTrans("models.classes.page-config", { + choice: 2, + }); + const transArchives = str(useTrans("back.archives")).ucFirst().value(); + const transTelescope = useTrans("back.telescope"); + + /** + * Get sidenav actions. + * + * @param ReactNode items + * @param string model + * @return + */ + const filterActions = ({ + items, + model, + }: { + items?: ReactNode[]; + model?: string; + }): ReactNode => { + let computedItems = items; + // Set create action by default. + if (!computedItems && model) { + computedItems = [ + authPolicies[model].create && ( + + ), + ]; + } + const visibleItems = computedItems + ? computedItems.filter(Boolean) + : null; + return visibleItems && visibleItems.length > 0 ? visibleItems : null; + }; + const navList: NavListProps = [ { - title: useTrans("back.pages"), + title: transPages, show: true, links: [ { - title: useTrans("back.dashboard"), + title: transDashboard, model: "dashboard", show: true, icon: HomeIcon, }, { - title: useTrans("back.your_website"), + title: transWebsite, link: "/", show: true, icon: SquareArrowOutUpRightIcon, }, { - title: str( - useTrans("models.classes.dynamic-page", { choice: 2 }), - ) - .ucFirst() - .value(), + title: str(transDynamicPages).ucFirst().value(), model: "dynamic-pages", show: authPolicies.dynamicPages.viewAny, icon: StickyNoteIcon, @@ -133,14 +233,20 @@ export default function AppSidebar() { items: [ authPolicies.dynamicPages.create && ( ), authPolicies.dynamicPages.viewTrash && ( @@ -151,15 +257,13 @@ export default function AppSidebar() { ], }, { - title: useTrans("back.modules"), + title: transModules, show: authPolicies.news.viewAny || auth.policies.contact_counts.viewAny, links: [ { - title: str(useTrans("models.classes.news", { choice: 2 })) - .ucFirst() - .value(), + title: str(transNews).ucFirst().value(), model: "news", activeCondition: [ route().current("back.news-categories.*"), @@ -172,44 +276,42 @@ export default function AppSidebar() { items: [ authPolicies.news.create && ( ), authPolicies.news.viewTrash && ( ), authPolicies.newsCategories.viewAny && ( ), authPolicies.newsTags.viewAny && ( @@ -218,19 +320,13 @@ export default function AppSidebar() { }), }, { - title: str(useTrans("models.classes.contact-count")) - .ucFirst() - .value(), + title: str(transContactCount).ucFirst().value(), model: "contact-counts", icon: ContactIcon, show: auth.policies.contact_counts.viewAny, }, { - title: str( - useTrans("models.classes.document", { choice: 2 }), - ) - .ucFirst() - .value(), + title: str(transDocuments).ucFirst().value(), model: "documents", activeCondition: [ route().current("back.document-categories.*"), @@ -243,21 +339,20 @@ export default function AppSidebar() { items: [ authPolicies.documents.create && ( ), authPolicies.documentCategories.create && ( @@ -265,15 +360,10 @@ export default function AppSidebar() { authPolicies.documentTags.create && ( @@ -282,12 +372,7 @@ export default function AppSidebar() { }), }, { - title: str( - useTrans("models.classes.company", { choice: 2 }), - ) - .ucFirst() - .plural() - .value(), + title: str(transCompanies).ucFirst().plural().value(), model: "companies", activeCondition: [ route().current("back.company-activities.*"), @@ -300,14 +385,20 @@ export default function AppSidebar() { items: [ authPolicies.companies.create && ( ), authPolicies.companies.viewTrash && ( @@ -315,15 +406,10 @@ export default function AppSidebar() { authPolicies.companyActivities.viewAny && ( @@ -331,15 +417,10 @@ export default function AppSidebar() { authPolicies.companySectors.viewAny && ( @@ -350,34 +431,24 @@ export default function AppSidebar() { ], }, { - title: str(useTrans("enums.user-role.admin")).plural().value(), + title: str(transAdmin).plural().value(), show: auth.user.role === enumUserRole.conceptor.value, links: [ { - title: str(useTrans("models.classes.user")) - .ucFirst() - .plural() - .value(), + title: str(transUsers).ucFirst().plural().value(), model: "users", icon: UsersIcon, show: authPolicies.users.viewAny, actions: (model: string) => filterActions({ model }), }, { - title: str(useTrans("models.classes.activity-log")) - .ucFirst() - .plural() - .value(), + title: str(transActivityLogs).ucFirst().plural().value(), model: "activity-logs", icon: HistoryIcon, show: authPolicies.activityLogs.viewAny, }, { - title: str( - useTrans("models.classes.page-config", { choice: 2 }), - ) - .ucFirst() - .value(), + title: str(transPageConfigs).ucFirst().value(), model: "page-configs", show: authPolicies.pageConfigs.viewAny, icon: CogIcon, @@ -386,23 +457,27 @@ export default function AppSidebar() { items: [ authPolicies.pageConfigs.create && ( ), authPolicies.pageConfigs.viewTrash && ( ), ], }), }, { - title: useTrans("back.telescope"), + title: transTelescope, link: route().has("telescope") ? route("telescope") : undefined, @@ -413,49 +488,6 @@ export default function AppSidebar() { }, ]; - const filterActions = ({ - items, - model, - }: { - items?: ReactNode[]; - model?: string; - }) => { - // Set create action by default. - if (!items && model) { - items = [ - authPolicies[model].create && ( - - ), - ]; - } - const visibleItems = items ? items.filter(Boolean) : null; - return visibleItems && visibleItems.length > 0 ? ( - <>{visibleItems} - ) : null; - }; - - function CustomDropdownMenuItem({ - route, - icon: Icon = Views.CREATION.icon, - trans = str(useTrans(`crud.actions.${Views.CREATION.label}`)) - .ucFirst() - .value(), - }: CustomDropdownMenuItemProps) { - return ( - - - - ); - } - useEffect(() => { if (prevStateRef.current !== state) { const elmts = document.querySelectorAll(".sidebar-transition"); @@ -497,110 +529,110 @@ export default function AppSidebar() { - {navGroup.links.map( - (navLink) => { - if (navLink.show) { - const isActive = - route().current( - `back.${navLink.model}.*`, - ) || - navLink.activeCondition?.some( - Boolean, - ); - const sidebarButton = - ( - + navLink.show, + ) + .map((navLink) => { + const isActive = + route().current( + `back.${navLink.model}.*`, + ) || + navLink.activeCondition?.some( + Boolean, + ); + const sidebarButton = ( + + {navLink.link ? ( + - {navLink.link ? ( - - - { - navLink.title - } - - ) : ( - - - { - navLink.title - } - + + { + navLink.title + } + + ) : ( + - ); - return ( - - {isCollapsed ? ( - - + + { + navLink.title + } + + )} + + ); + return ( + + {isCollapsed ? ( + + + { + sidebarButton + } + + +

+ { + navLink.title + } +

+
+
+ ) : ( + sidebarButton + )} + {navLink.model && + navLink.actions?.( + navLink.model, + ) && ( + + - { - sidebarButton - } - - -

- { - navLink.title - } -

-
- - ) : ( - sidebarButton + + + +
+ + {navLink.actions( + navLink.model, + )} + +
)} - {navLink.model && - navLink.actions?.( - navLink.model, - ) && ( - - - - - - - - {navLink.actions( - navLink.model, - )} - - - )} -
- ); - } - }, - )} + + ); + })}
diff --git a/resources/ts/back/Components/ModelActions.tsx b/resources/ts/back/Components/ModelActions.tsx index ea5744b0..319cba59 100644 --- a/resources/ts/back/Components/ModelActions.tsx +++ b/resources/ts/back/Components/ModelActions.tsx @@ -1,8 +1,9 @@ // * Packages import { Link, useForm } from "@inertiajs/react"; import { route } from "ziggy-js"; -import { FormEvent, useState } from "react"; +import { FormEvent, ReactNode, useState } from "react"; import { MoreHorizontalIcon } from "lucide-react"; +import type { VisitOptions } from "@inertiajs/core"; // * Components shadcn import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; @@ -27,13 +28,281 @@ import { useFormatedDate } from "@/hooks/use-formated-value"; import { useToast } from "@/hooks/use-toast"; import useTrans from "@/hooks/use-translations"; import str from "@/hooks/use-string"; -import { Model, Views as ViewsType } from "@/back/types/global"; +import { DefaultModel, Views as ViewsType } from "@/back/types/global"; import { Views } from "@/lib/utils"; +import { ToastActionElement, ToastProps } from "@/components/ui/toast"; + +interface Translations { + successfulArchivation: string; + successfulArchivationDetails: string; + canceledArchivation: string; + canceledArchivationDetails: string; + successfulRestoration: string; + successfulRestorationDetails: string; + canceledRestoration: string; + canceledRestorationDetails: string; + successfulDeletion: string; + successfulDeletionDetails: string; + canceledDeletion: string; + canceledDeletionDetails: string; +} + +type ToasterToast = ToastProps & { + id: string; + title?: ReactNode; + description?: ReactNode; + action?: ToastActionElement; +}; + +/** + * Button for GET action. + * @param action ViewsType[number] + * @param modelRoute string + * @param model DefaultModel + * @returns Element + */ +function GetAction({ + action, + modelRoute, + model, +}: { + action: ViewsType[number]; + modelRoute: string; + model: DefaultModel; +}) { + return ( + + ); +} + +/** + * Action call on model. + * @param event FormEvent + * @param action ViewsType[number] + * @param modelRoute string + * @param model DefaultModel + * @param setOpen (value: boolean) => void + * @param translations Translations + * @param toast + */ +const actionOnModel = ( + /* eslint-disable no-unused-vars */ + event: FormEvent, + action: ViewsType[number], + modelRoute: string, + model: DefaultModel, + setOpen: (value: boolean) => void, + translations: Translations, + toast: ({ ...props }: Omit) => { + id: string; + dismiss: () => void; + update: (propsChild: ToasterToast) => void; + }, + destroy: (url: string, options?: VisitOptions) => void, + patch: (url: string, options?: VisitOptions) => void, + /* eslint-enable no-unused-vars */ +) => { + event.preventDefault(); + + const routeAction = route(`back.${modelRoute}.${action.value}`, model.id); + + // All post actions mapping. + const actionMap: Record< + ViewsType[number]["label"], + { + method: typeof destroy | typeof patch; + success: { title: string; description: string }; + error: { title: string; description: string }; + } + > = { + [Views.ARCHIVATION.label]: { + method: destroy, + success: { + title: translations.successfulArchivation, + description: translations.successfulArchivationDetails, + }, + error: { + title: translations.canceledArchivation, + description: translations.canceledArchivationDetails, + }, + }, + [Views.RESTORATION.label]: { + method: patch, + success: { + title: translations.successfulRestoration, + description: translations.successfulRestorationDetails, + }, + error: { + title: translations.canceledRestoration, + description: translations.canceledRestorationDetails, + }, + }, + [Views.DELETION.label || Views.FORCEDELETION.label]: { + method: destroy, + success: { + title: translations.successfulDeletion, + description: translations.successfulDeletionDetails, + }, + error: { + title: translations.canceledDeletion, + description: translations.canceledDeletionDetails, + }, + }, + }; + + // Check if route exist. + const config = actionMap[action.label]; + if (!config) { + throw new Error(`Unknown action: ${action.label}`); + } + + // Close the dropdown. + setOpen(false); + + // Execute the call. + config.method(routeAction, { + preserveScroll: true, + onSuccess: () => toast(config.success), + onError: () => toast({ variant: "destructive", ...config.error }), + }); +}; + +/** + * Button for POST action. + * @param action ViewsType[number] + * @param modelRoute string + * @param model DefaultModel + * @param setOpen (value: boolean) => void + * @returns Element + */ +function PostAction({ + action, + modelRoute, + model, + setOpen, +}: { + action: ViewsType[number]; + modelRoute: string; + model: DefaultModel; + // eslint-disable-next-line no-unused-vars + setOpen: (value: boolean) => void; +}) { + const { delete: destroy, patch } = useForm(); + + const isRestoreAction = action.label === Views.RESTORATION.label; + const isArchiveAction = action.label === Views.ARCHIVATION.label; + const btnVariantType = isRestoreAction ? "ghost" : "destructive"; + let translationKeyBtnConfirm: string; + const { toast } = useToast(); + if (isRestoreAction) { + translationKeyBtnConfirm = "back.yes_restore"; + } else if (isArchiveAction) { + translationKeyBtnConfirm = "back.yes_archive"; + } else { + translationKeyBtnConfirm = "back.yes_delete"; + } + + const btnConfirm = useTrans(translationKeyBtnConfirm); + + let translationKeyBtnDesc = "back.lost_data"; + + if (isRestoreAction) { + translationKeyBtnDesc = "back.restore_data"; + } else if (isArchiveAction) { + translationKeyBtnDesc = "back.trash_data"; + } + + const btnDesc = useTrans(translationKeyBtnDesc); + + // Translations for actionOnModel. + const translations: Translations = { + successfulArchivation: useTrans("crud.messages.successful_archivation"), + successfulArchivationDetails: useTrans( + "crud.messages.successful_archivation_details", + ), + canceledArchivation: useTrans("crud.messages.canceled_archivation"), + canceledArchivationDetails: useTrans( + "crud.messages.canceled_archivation_details", + ), + successfulRestoration: useTrans("crud.messages.successful_restoration"), + successfulRestorationDetails: useTrans( + "crud.messages.successful_restoration_details", + ), + canceledRestoration: useTrans("crud.messages.canceled_restoration"), + canceledRestorationDetails: useTrans( + "crud.messages.canceled_restoration_details", + ), + successfulDeletion: useTrans("crud.messages.successful_deletion"), + successfulDeletionDetails: useTrans( + "crud.messages.successful_deletion_details", + ), + canceledDeletion: useTrans("crud.messages.canceled_deletion"), + canceledDeletionDetails: useTrans( + "crud.messages.canceled_deletion_details", + ), + }; + + return ( + + + + + + + {useTrans("back.are_you_sure")} + {btnDesc} + + + + + + + actionOnModel( + event, + action, + modelRoute, + model, + setOpen, + translations, + toast, + destroy, + patch, + ) + } + className="w-fit mb-2 sm:mb-0" + > + + + + + + ); +} interface ModelActionsProps { actions?: ViewsType; modelRoute: string; - model: Model; + model: DefaultModel; modelUpdatedAt?: string; inDialog?: boolean; message?: string; @@ -54,225 +323,52 @@ export default function ModelActions({ (action) => model.policies[action.policy as keyof typeof model.policies], ); - const { delete: destroy, patch } = useForm(); - const { toast } = useToast(); - - // Translations. - const successfulArchivation = useTrans( - "crud.messages.successful_archivation", - ); - const successfulArchivationDetails = useTrans( - "crud.messages.successful_archivation_details", - ); - const canceledArchivation = useTrans("crud.messages.canceled_archivation"); - const canceledArchivationDetails = useTrans( - "crud.messages.canceled_archivation_details", - ); - - const successfulRestoration = useTrans( - "crud.messages.successful_restoration", - ); - const successfulRestorationDetails = useTrans( - "crud.messages.successful_restoration_details", - ); - const canceledRestoration = useTrans("crud.messages.canceled_restoration"); - const canceledRestorationDetails = useTrans( - "crud.messages.canceled_restoration_details", - ); - - const successfulDeletion = useTrans("crud.messages.successful_deletion"); - const successfulDeletionDetails = useTrans( - "crud.messages.successful_deletion_details", - ); - const canceledDeletion = useTrans("crud.messages.canceled_deletion"); - const canceledDeletionDetails = useTrans( - "crud.messages.canceled_deletion_details", - ); - - /** - * Action call on model. - * @param event FormEvent - * @param action ViewsType[number] - */ - const actionOnModel = ( - event: FormEvent, - action: ViewsType[number], - ) => { - event.preventDefault(); - - const routeAction = route( - `back.${modelRoute}.${action.value}`, - model.id, - ); - - // All post actions mapping. - const actionMap: Record< - ViewsType[number]["label"], - { - method: typeof destroy | typeof patch; - success: { title: string; description: string }; - error: { title: string; description: string }; - } - > = { - [Views.ARCHIVATION.label]: { - method: destroy, - success: { - title: successfulArchivation, - description: successfulArchivationDetails, - }, - error: { - title: canceledArchivation, - description: canceledArchivationDetails, - }, - }, - [Views.RESTORATION.label]: { - method: patch, - success: { - title: successfulRestoration, - description: successfulRestorationDetails, - }, - error: { - title: canceledRestoration, - description: canceledRestorationDetails, - }, - }, - [Views.DELETION.label || Views.FORCEDELETION.label]: { - method: destroy, - success: { - title: successfulDeletion, - description: successfulDeletionDetails, - }, - error: { - title: canceledDeletion, - description: canceledDeletionDetails, - }, - }, - }; - - // Check if route exist. - const config = actionMap[action.label]; - if (!config) { - throw new Error(`Unknown action: ${action.label}`); - } - - // Close the dropdown. - setOpen(false); - - // Execute the call. - config.method(routeAction, { - preserveScroll: true, - onSuccess: () => toast(config.success), - onError: () => toast({ variant: "destructive", ...config.error }), - }); - }; /** * Get all action buttons. * @returns Element[] */ const AllBtnActions = () => { - return allowedActions.map((action, index) => ( -
- {action.value === Views.ARCHIVATION.value || - action.value === Views.RESTORATION.value || - action.value === Views.DELETION.value || - action.value === Views.FORCEDELETION.value ? ( - inDialog && index != 0 ? ( - <> - - - - ) : ( - - ) - ) : ( - - )} -
- )); + const postActionValues = new Set([ + Views.ARCHIVATION.value, + Views.RESTORATION.value, + Views.DELETION.value, + Views.FORCEDELETION.value, + ]); + + return allowedActions.map((action) => { + const isPostAction = postActionValues.has(action.value); + const showSeparator = + inDialog && + isPostAction && + action.value !== allowedActions[0].value; + + const content = isPostAction ? ( + <> + {showSeparator && } + + + ) : ( + + ); + + return ( +
+ {content} +
+ ); + }); }; - /** - * Button for POST action. - * @param action ViewsType[number] - * @returns Element - */ - function PostAction({ action }: { action: ViewsType[number] }) { - const isRestoreAction = action.label === Views.RESTORATION.label; - const isArchiveAction = action.label === Views.ARCHIVATION.label; - const btnVariantType = isRestoreAction ? "ghost" : "destructive"; - const btnConfirm = useTrans( - isRestoreAction - ? "back.yes_restore" - : isArchiveAction - ? "back.yes_archive" - : "back.yes_delete", - ); - const btnDesc = useTrans( - isRestoreAction - ? "back.restore_data" - : isArchiveAction - ? "back.trash_data" - : "back.lost_data", - ); - return ( - - - - - - - - {useTrans("back.are_you_sure")} - - {btnDesc} - - - - - -
actionOnModel(event, action)} - className="w-fit mb-2 sm:mb-0" - > - -
-
-
-
- ); - } - - /** - * Button for GET action. - * @param action ViewsType[number] - * @returns Element - */ - function GetAction({ action }: { action: ViewsType[number] }) { - return ( - - ); - } - return (
{ + const GetTranslation = (name: string, options?: UseTransOptions) => { + return useTrans(name, options); + }; + const GetFormatedDate = ( + date: string, + timezone?: string, + time?: boolean, + ) => { + return useFormatedDate(date, timezone, time); + }; + return [ { accessorKey: "title", header: ({ column }: { column: { id: string } }) => ( ), - cell: ({ row }: { row: Row }) => { - return renderTranslationCell(row.getValue("title")); - }, + cell: ({ row }: { row: Row }) => ( + + ), }, { accessorKey: "status", header: ({ column }: { column: { id: string } }) => ( }) => { return ( - + ); }, }, @@ -59,7 +67,9 @@ function AssociatedNewsList(props: Props) { accessorKey: "updated_at", header: ({ column }: { column: { id: string } }) => ( }) => ( - {useFormatedDate(row.getValue("updated_at"))} + {GetFormatedDate(row.getValue("updated_at"))} ), }, ]; +}; +function AssociatedNewsList(props: Props) { + const { news } = props; + + const columns = getColumns(); return ( <> diff --git a/resources/ts/back/Components/PageConfigs/AddNewFieldBtn.tsx b/resources/ts/back/Components/PageConfigs/AddNewFieldBtn.tsx index 3cc38c94..5da118e3 100644 --- a/resources/ts/back/Components/PageConfigs/AddNewFieldBtn.tsx +++ b/resources/ts/back/Components/PageConfigs/AddNewFieldBtn.tsx @@ -5,7 +5,10 @@ import { VariantProps } from "class-variance-authority"; import { Button, buttonVariants } from "@/components/ui/button"; // * Other import useTrans from "@/hooks/use-translations"; -import { ConfigInternalField, DynamicInternalField } from "@/back/types/global"; +import { + ConfigInternalField, + DynamicInternalField, +} from "@/back/types/dynamic-pages"; type AddNewFieldBtnProps = { array: ConfigInternalField[] | DynamicInternalField[] | undefined; diff --git a/resources/ts/back/Components/PageConfigs/ConfigDetails.tsx b/resources/ts/back/Components/PageConfigs/ConfigDetails.tsx index b12370f6..8475504e 100644 --- a/resources/ts/back/Components/PageConfigs/ConfigDetails.tsx +++ b/resources/ts/back/Components/PageConfigs/ConfigDetails.tsx @@ -11,7 +11,7 @@ import { // * Other import str from "@/hooks/use-string"; import useTrans from "@/hooks/use-translations"; -import { ConfigField, DynamicFieldTypes } from "@/back/types/global"; +import { ConfigField, DynamicFieldTypes } from "@/back/types/dynamic-pages"; import { cn } from "@/lib/utils"; type ConfigDetailsProps = { @@ -43,7 +43,7 @@ export default function ConfigDetails({ config }: ConfigDetailsProps) { const hasChildren = !!field.children?.length; const accordionKey = `${field.key}-${index}`; - const rowContent = (hasChildren: boolean = false) => ( + const rowContent = (targetHasChildren: boolean = false) => (

{getEnumDynamicFieldType(field.type)?.label} @@ -55,7 +55,7 @@ export default function ConfigDetails({ config }: ConfigDetailsProps) {

{field.validation || "—"}
- {!hasChildren &&
} + {!targetHasChildren &&
}
); @@ -80,7 +80,7 @@ export default function ConfigDetails({ config }: ConfigDetailsProps) { )} {hasChildren && ( - +
{RenderRows(field.children!, false)}
diff --git a/resources/ts/back/Components/PageConfigs/DynamicFields.tsx b/resources/ts/back/Components/PageConfigs/DynamicFields.tsx index c0b57774..5f63cf5c 100644 --- a/resources/ts/back/Components/PageConfigs/DynamicFields.tsx +++ b/resources/ts/back/Components/PageConfigs/DynamicFields.tsx @@ -24,7 +24,7 @@ import { ConfigField, ConfigInternalField, DynamicFieldTypes, -} from "@/back/types/global"; +} from "@/back/types/dynamic-pages"; import useTrans from "@/hooks/use-translations"; import str from "@/hooks/use-string"; @@ -71,7 +71,8 @@ export default function DynamicFields({ let currentId = startId; const withIds = fields.map((field) => { - const id = currentId++; + currentId += 1; + const id = currentId; // Add the key in sensitive array. const currentParentKey = `${parentKey}.${field.key}`; @@ -197,18 +198,6 @@ export default function DynamicFields({ ); }; - /** - * Remove a specific field. - * @param fieldId string - * @returns void - */ - const removeField = (fieldId: number): void => { - const targetField = fields.filter((field) => field.id === fieldId)[0]; - - removeSensitiveField(targetField.key); - setFields((prev) => prev.filter((field) => field.id !== fieldId)); - }; - /** * Remove sensitive field list. * @param keyToRemove string @@ -221,6 +210,18 @@ export default function DynamicFields({ setLocalErrors({}); }; + /** + * Remove a specific field. + * @param fieldId string + * @returns void + */ + const removeField = (fieldId: number): void => { + const targetField = fields.filter((field) => field.id === fieldId)[0]; + + removeSensitiveField(targetField.key); + setFields((prev) => prev.filter((field) => field.id !== fieldId)); + }; + /** * Save the new order. * @param event DragEndEvent @@ -249,11 +250,13 @@ export default function DynamicFields({ /** * Remove all local ids. - * @param fields ConfigInternalField[] + * @param currentFields ConfigInternalField[] * @returns */ - const removeIds = (fields: ConfigInternalField[]): ConfigField[] => { - return fields.map(({ id, children, ...rest }) => ({ + const removeIds = (currentFields: ConfigInternalField[]): ConfigField[] => { + // Delete the id, keep only the children if they are present, and keep the rest. + // eslint-disable-next-line @typescript-eslint/no-unused-vars + return currentFields.map(({ id, children, ...rest }) => ({ ...rest, ...(children ? { children: removeIds(children) } : {}), })) as ConfigField[]; @@ -267,11 +270,13 @@ export default function DynamicFields({ setData(name, payload); prevValueRef.current = payloadStr; } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [fields]); // Update fields when copying config from another locale. useEffect(() => { setFields(() => addIdsRecursively(value ?? [])); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedConfig]); return ( diff --git a/resources/ts/back/Components/PageConfigs/FieldGroup.tsx b/resources/ts/back/Components/PageConfigs/FieldGroup.tsx index 18733bf4..67ac6e85 100644 --- a/resources/ts/back/Components/PageConfigs/FieldGroup.tsx +++ b/resources/ts/back/Components/PageConfigs/FieldGroup.tsx @@ -1,3 +1,4 @@ +// * Packages import { Dispatch, SetStateAction, useState } from "react"; import { DndContext, @@ -33,11 +34,12 @@ import { ConfigField, ConfigInternalField, DynamicFieldTypes, -} from "@/back/types/global"; +} from "@/back/types/dynamic-pages"; import useTrans from "@/hooks/use-translations"; import str from "@/hooks/use-string"; import { cn } from "@/lib/utils"; +/* eslint-disable no-unused-vars */ type FieldGroupProps = { currentId: number; currentIndex: number; @@ -62,6 +64,7 @@ type FieldGroupProps = { setParentAccordionState: (key: string) => void; limitMaxChildren: number; }; +/* eslint-enable no-unused-vars */ export default function FieldGroup({ currentId, @@ -131,6 +134,9 @@ export default function FieldGroup({ : currentField.key; }); + //* Translations + const transKeyAlreadyPresent = useTrans("validation.key_already_present"); + // Set the complete key (prefix + editable) that will be sent to the backend. const completeKey = `${enumCurrentBase}${editableKey}`; @@ -217,7 +223,7 @@ export default function FieldGroup({ */ const updatingType = (value: string) => { const selectedType = Object.values(enumDynamicFieldTypes).find( - (def) => def.value === parseInt(value), + (def) => def.value === Number(value), ); if (selectedType) { const resetCommon = { @@ -259,8 +265,10 @@ export default function FieldGroup({ variant="outline" size="icon" disabled={parentAccordionState !== ""} + /* eslint-disable react/jsx-props-no-spreading */ {...attributes} {...listeners} + /* eslint-enable react/jsx-props-no-spreading */ />
@@ -352,7 +360,7 @@ export default function FieldGroup({ )} placeholder="ex: field_1" value={editableKey} - setData={(_, value) => { + setData={(_unused, value) => { const newCompleteKey = `${enumCurrentBase}${value}`; if ( sensitiveField.includes( @@ -362,15 +370,14 @@ export default function FieldGroup({ setLocalErrors((prev) => ({ ...prev, [`${currentFieldKey}.${currentIndex}.key`]: - useTrans( - "validation.key_already_present", - ), + transKeyAlreadyPresent, })); } else { setLocalErrors((prev) => { const { [`${currentFieldKey}.${currentIndex}.key`]: - _, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + removedKey, ...rest } = prev; return rest; diff --git a/resources/ts/back/Components/PageConfigs/ImportConfigJson.tsx b/resources/ts/back/Components/PageConfigs/ImportConfigJson.tsx index 49418973..fdba1747 100644 --- a/resources/ts/back/Components/PageConfigs/ImportConfigJson.tsx +++ b/resources/ts/back/Components/PageConfigs/ImportConfigJson.tsx @@ -1,5 +1,5 @@ // * Packages -import { useRef, useState } from "react"; +import { ChangeEvent, DragEvent, useRef, useState } from "react"; import { JsonEditor, IconReplacements, @@ -41,6 +41,7 @@ type JsonObject = { }; type ImportConfigJsonProps = { + // eslint-disable-next-line no-unused-vars onConfirm: (data: JsonObject[], overwrite: boolean) => void; }; @@ -91,9 +92,25 @@ export default function ImportConfigJson({ onConfirm }: ImportConfigJsonProps) { * @param key string * @return string */ - const getTranslation = (key: string): string => + const useGetTranslation = (key: string): string => str(useTrans(key)).ucFirst().value(); + const transJsonEditorMaxSize = useTrans( + "back.json_editor.validation.max_size", + { + params: { value: "2MB" }, + }, + ); + const transJsonEditorMimes = useTrans("back.json_editor.validation.mimes", { + params: { values: ".json" }, + }); + const transJsonEditorInvalidFileJson = useTrans( + "back.json_editor.validation.invalid_file_json", + ); + const transInvalidContentJson = useTrans( + "back.json_editor.validation.invalid_content_json", + ); + // Custom icons. const icons: IconReplacements = { add: , @@ -107,26 +124,28 @@ export default function ImportConfigJson({ onConfirm }: ImportConfigJsonProps) { // Custom translations. const translations: LocalisedStrings = { - ITEM_SINGLE: getTranslation("back.json_editor.item_single"), - ITEMS_MULTIPLE: getTranslation("back.json_editor.item_multiple"), - KEY_NEW: getTranslation("back.json_editor.key_new"), - KEY_SELECT: getTranslation("back.json_editor.key_select"), - NO_KEY_OPTIONS: getTranslation("back.json_editor.no_key_options"), - ERROR_KEY_EXISTS: getTranslation("back.json_editor.error_key_exists"), - ERROR_INVALID_JSON: getTranslation( + ITEM_SINGLE: useGetTranslation("back.json_editor.item_single"), + ITEMS_MULTIPLE: useGetTranslation("back.json_editor.item_multiple"), + KEY_NEW: useGetTranslation("back.json_editor.key_new"), + KEY_SELECT: useGetTranslation("back.json_editor.key_select"), + NO_KEY_OPTIONS: useGetTranslation("back.json_editor.no_key_options"), + ERROR_KEY_EXISTS: useGetTranslation( + "back.json_editor.error_key_exists", + ), + ERROR_INVALID_JSON: useGetTranslation( "back.json_editor.error_invalid_json", ), - ERROR_UPDATE: getTranslation("back.json_editor.error_update"), - ERROR_DELETE: getTranslation("back.json_editor.error_delete"), - ERROR_ADD: getTranslation("back.json_editor.error_add"), - DEFAULT_STRING: getTranslation("back.json_editor.default_string"), - DEFAULT_NEW_KEY: getTranslation("back.json_editor.default_new_key"), - SHOW_LESS: getTranslation("back.json_editor.show_less"), - EMPTY_STRING: getTranslation("back.json_editor.empty_string"), - TOOLTIP_COPY: getTranslation("back.json_editor.copy"), - TOOLTIP_EDIT: getTranslation("back.json_editor.edit"), - TOOLTIP_DELETE: getTranslation("back.json_editor.delete"), - TOOLTIP_ADD: getTranslation("back.json_editor.add"), + ERROR_UPDATE: useGetTranslation("back.json_editor.error_update"), + ERROR_DELETE: useGetTranslation("back.json_editor.error_delete"), + ERROR_ADD: useGetTranslation("back.json_editor.error_add"), + DEFAULT_STRING: useGetTranslation("back.json_editor.default_string"), + DEFAULT_NEW_KEY: useGetTranslation("back.json_editor.default_new_key"), + SHOW_LESS: useGetTranslation("back.json_editor.show_less"), + EMPTY_STRING: useGetTranslation("back.json_editor.empty_string"), + TOOLTIP_COPY: useGetTranslation("back.json_editor.copy"), + TOOLTIP_EDIT: useGetTranslation("back.json_editor.edit"), + TOOLTIP_DELETE: useGetTranslation("back.json_editor.delete"), + TOOLTIP_ADD: useGetTranslation("back.json_editor.add"), }; /** @@ -137,7 +156,6 @@ export default function ImportConfigJson({ onConfirm }: ImportConfigJsonProps) { const openFilePicker = (): void => { inputRef.current?.click(); }; - /** * Set json from content file. * @@ -148,12 +166,7 @@ export default function ImportConfigJson({ onConfirm }: ImportConfigJsonProps) { if (file.size > maxSize) { setGlobalErrors((prev) => ({ ...prev, - "global-error": useTrans( - "back.json_editor.validation.max_size", - { - params: { value: "2MB" }, - }, - ), + "global-error": transJsonEditorMaxSize, })); return; } @@ -161,9 +174,7 @@ export default function ImportConfigJson({ onConfirm }: ImportConfigJsonProps) { if (!file.name.endsWith(".json")) { setGlobalErrors((prev) => ({ ...prev, - "global-error": useTrans("back.json_editor.validation.mimes", { - params: { values: ".json" }, - }), + "global-error": transJsonEditorMimes, })); return; } @@ -181,20 +192,33 @@ export default function ImportConfigJson({ onConfirm }: ImportConfigJsonProps) { } catch { setGlobalErrors((prev) => ({ ...prev, - "global-error": useTrans( - "back.json_editor.validation.invalid_file_json", - ), + "global-error": transJsonEditorInvalidFileJson, })); } }; + /** + * Reset component. + * + * @return void + */ + const resetComponent = (): void => { + setIsOpen(false); + setTimeout(() => { + setJsonText([]); + setGlobalErrors({}); + setEditorErrors({}); + setOverwrite(false); + }, 200); + }; + /** * Action when changing file. * * @param event * @return void */ - const handleFileChange = (e: React.ChangeEvent): void => { + const handleFileChange = (e: ChangeEvent): void => { const file = e.target.files?.[0]; if (file) processFile(file); }; @@ -205,7 +229,7 @@ export default function ImportConfigJson({ onConfirm }: ImportConfigJsonProps) { * @param event * @return void */ - const handleDrop = (event: React.DragEvent): void => { + const handleDrop = (event: DragEvent): void => { event.preventDefault(); setIsDragging(false); @@ -227,28 +251,11 @@ export default function ImportConfigJson({ onConfirm }: ImportConfigJsonProps) { } catch { setEditorErrors((prev) => ({ ...prev, - "editor-error": useTrans( - "back.json_editor.validation.invalid_content_json", - ), + "editor-error": transInvalidContentJson, })); } }; - /** - * Reset component. - * - * @return void - */ - const resetComponent = (): void => { - setIsOpen(false); - setTimeout(() => { - setJsonText([]); - setGlobalErrors({}); - setEditorErrors({}); - setOverwrite(false); - }, 200); - }; - return ( <> {/* Dropzone */} @@ -269,7 +276,7 @@ export default function ImportConfigJson({ onConfirm }: ImportConfigJsonProps) { setIsDragging(true); }} onDragLeave={() => setIsDragging(false)} - // @ts-ignore + // @ts-expect-error function return are not compatible for ts onDrop={handleDrop} > - {getTranslation("back.json_editor.modal_title")} + {useGetTranslation("back.json_editor.modal_title")} - {getTranslation( + {useGetTranslation( "back.json_editor.modal_description", )} @@ -333,17 +340,19 @@ export default function ImportConfigJson({ onConfirm }: ImportConfigJsonProps) { setOverwrite(value)} />
{ - return useTrans("pagination.informations", { - params: { - total: String(modelsPaginate.total), - page: String(modelsPaginate.last_page), - }, - choice: modelsPaginate.last_page > 1 ? 2 : 1, - }); - }; return ( modelsPaginate.total > 1 && (
@@ -26,7 +17,13 @@ export default function PaginationWrapper({ total={modelsPaginate.total} />

- {GetTranslation()} + {GetTranslation("pagination.informations", { + params: { + total: String(modelsPaginate.total), + page: String(modelsPaginate.last_page), + }, + choice: modelsPaginate.last_page > 1 ? 2 : 1, + })}

diff --git a/resources/ts/back/Components/SubmitButton.tsx b/resources/ts/back/Components/SubmitButton.tsx index f215882a..039dc4df 100644 --- a/resources/ts/back/Components/SubmitButton.tsx +++ b/resources/ts/back/Components/SubmitButton.tsx @@ -1,6 +1,7 @@ +// * Packages +import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; import str from "@/hooks/use-string"; import useTrans from "@/hooks/use-translations"; -import BtnOptionnalLinkWithTooltip from "./BtnOptionnalLinkWithTooltip"; function SubmitButton({ isInDialog = false, diff --git a/resources/ts/back/Components/Table/DetailsTable.tsx b/resources/ts/back/Components/Table/DetailsTable.tsx index 4ef4c958..6a16fc56 100644 --- a/resources/ts/back/Components/Table/DetailsTable.tsx +++ b/resources/ts/back/Components/Table/DetailsTable.tsx @@ -1,3 +1,6 @@ +// * Packages +import { ReactNode } from "react"; +// * Components shadcn import { Table, TableBody, @@ -6,9 +9,9 @@ import { TableHeader, TableRow, } from "@/components/ui/table"; +// * Other import useTrans from "@/hooks/use-translations"; import { Model } from "@/back/types/global"; -import { ReactNode } from "react"; interface DetailsTableProps { columns: { diff --git a/resources/ts/lib/translationCellFallback.tsx b/resources/ts/back/Components/Table/RenderTranslationCell.tsx similarity index 75% rename from resources/ts/lib/translationCellFallback.tsx rename to resources/ts/back/Components/Table/RenderTranslationCell.tsx index 2d99146b..03f120ad 100644 --- a/resources/ts/lib/translationCellFallback.tsx +++ b/resources/ts/back/Components/Table/RenderTranslationCell.tsx @@ -1,7 +1,10 @@ -import { AppEnv, TranslationField } from "@/back/types/global"; -import useTrans from "@/hooks/use-translations"; +// * Packages import { usePage } from "@inertiajs/react"; import { ReactNode } from "react"; +// * Other +import { AppEnv } from "@/back/types/global"; +import { TranslationField } from "@/back/types/i18n"; +import useTrans from "@/hooks/use-translations"; /** * Resolves the best translation value for the current locale. @@ -14,9 +17,9 @@ import { ReactNode } from "react"; * @return An object with the resolved value and optional fallback locale, * or null if no translation is available */ -const getTranslationValue = ( +function useGetTranslationValue( value?: TranslationField, -): { value: string; locale?: string } | null => { +): { value: string; locale?: string } | null { const { appEnv } = usePage().props as unknown as { appEnv: AppEnv; }; @@ -29,7 +32,7 @@ const getTranslationValue = ( if (!fallback) return null; return { value: fallback[1], locale: fallback[0] }; -}; +} /** * Renders a table cell displaying a translated value. @@ -41,15 +44,16 @@ const getTranslationValue = ( * @param value - Translation field containing localized values * @return A ReactNode ready to be rendered inside a table cell */ -export const renderTranslationCell = (value?: TranslationField): ReactNode => { - const result = getTranslationValue(value); +export default function RenderTranslationCell({ + value, +}: { + value?: TranslationField; +}): ReactNode { + const result = useGetTranslationValue(value); + const notKnown = useTrans("back.table_not_known"); if (!result) { - return ( -

- {useTrans("back.table_not_known")} -

- ); + return

{notKnown}

; } if (!result.locale) { @@ -62,4 +66,4 @@ export const renderTranslationCell = (value?: TranslationField): ReactNode => { {result.value} ); -}; +} diff --git a/resources/ts/back/Components/WordCounter.tsx b/resources/ts/back/Components/WordCounter.tsx index 10e967ec..a822a319 100644 --- a/resources/ts/back/Components/WordCounter.tsx +++ b/resources/ts/back/Components/WordCounter.tsx @@ -1,3 +1,4 @@ +// * Packages import useTrans from "@/hooks/use-translations"; export default function WordCounter({ value }: { value: string }) { diff --git a/resources/ts/back/Layouts/AuthenticatedLayout.tsx b/resources/ts/back/Layouts/AuthenticatedLayout.tsx index e9bfd943..bd7a063d 100644 --- a/resources/ts/back/Layouts/AuthenticatedLayout.tsx +++ b/resources/ts/back/Layouts/AuthenticatedLayout.tsx @@ -19,38 +19,6 @@ type AuthenticatedLayoutProps = { children: ReactNode; }; -export default function AuthenticatedLayout({ - title, - beforeNode, - afterNode, - children, -}: AuthenticatedLayoutProps) { - const isCollapsed = - typeof window !== "undefined" - ? localStorage.getItem("vite-ui-collapsed") - : null; - - const { locale } = usePage().props.appEnv as unknown as { - locale: string; - }; - - return ( - - - - - {children} - - - - - ); -} - function LayoutContent({ title, beforeNode, @@ -59,11 +27,13 @@ function LayoutContent({ }: Omit & { children: ReactNode }) { const { open } = useSidebar(); const [isSm, setIsSm] = useState(false); - const sidebarVar = !isSm - ? open + + let sidebarVar = "0px"; + if (!isSm) { + sidebarVar = open ? "var(--sidebar-width)" - : "var(--sidebar-width-icon)" - : "0px"; + : "var(--sidebar-width-icon)"; + } useEffect(() => { const mediaQuery = window.matchMedia("(max-width: 767px)"); @@ -109,3 +79,35 @@ function LayoutContent({ ); } + +export default function AuthenticatedLayout({ + title, + beforeNode, + afterNode, + children, +}: AuthenticatedLayoutProps) { + const isCollapsed = + typeof window !== "undefined" + ? localStorage.getItem("vite-ui-collapsed") + : null; + + const { locale } = usePage().props.appEnv as unknown as { + locale: string; + }; + + return ( + + + + + {children} + + + + + ); +} diff --git a/resources/ts/back/Layouts/GuestLayout.tsx b/resources/ts/back/Layouts/GuestLayout.tsx index 84197a77..4091a6fb 100644 --- a/resources/ts/back/Layouts/GuestLayout.tsx +++ b/resources/ts/back/Layouts/GuestLayout.tsx @@ -4,8 +4,8 @@ import { ReactNode } from "react"; import { Card } from "@/components/ui/card"; // * Other import ApplicationLogo from "@/back/Components/ApplicationLogo"; -import { ThemeProvider } from "@/back/providers/ThemeProvider"; import AppSettings from "@/back/Components/Layout/AppSettings"; +import { ThemeProvider } from "@/back/providers/ThemeProvider"; export default function GuestLayout({ children }: { children: ReactNode }) { return ( diff --git a/resources/ts/back/Pages/ActivityLogs/Index.tsx b/resources/ts/back/Pages/ActivityLogs/Index.tsx index 5afbbcc7..9fcaade9 100644 --- a/resources/ts/back/Pages/ActivityLogs/Index.tsx +++ b/resources/ts/back/Pages/ActivityLogs/Index.tsx @@ -9,57 +9,59 @@ import DataTable from "@/back/Components/Table/DataTable"; import DataTableSearch from "@/back/Components/Table/DataTableSearch"; import DataTableFilterButton from "@/back/Components/Table/DataTableFilterButton"; import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; -import { Views } from "@/lib/utils"; -import { ActivityLogModel, Resource } from "@/back/types/global"; +import { GetFormatedDate, GetTranslation, Views } from "@/lib/utils"; +import { Resource } from "@/back/types/global"; +import { ActivityLogModel } from "@/back/types/activity-logs"; import str from "@/hooks/use-string"; import useTrans from "@/hooks/use-translations"; -import { useFormatedDate } from "@/hooks/use-formated-value"; +import { Row } from "@tanstack/react-table"; + +type ActivityLogsEventEnumProps = Record< + string, + { value: number; name: string; label: string; tailwindclass: string } +>; type ActivityLogsIndexProps = { activitylogModels: Resource; searchFields: string; - activityLogsEventEnum: Record< - string, - { value: number; name: string; label: string; tailwindclass: string } - >; + activityLogsEventEnum: ActivityLogsEventEnumProps; }; -export default function Index({ - activitylogModels, - searchFields, - activityLogsEventEnum, -}: ActivityLogsIndexProps) { - const actionModels = [Views.VISUALIZATION]; - const columns = [ +const getColumns = (activityLogsEventEnum: ActivityLogsEventEnumProps) => { + return [ { accessorKey: "event", header: ({ column }: { column: { id: string } }) => ( ), - cell: ({ - row, - }: { - row: { original: ActivityLogModel; getValue: Function }; - }) => { - const currentEvent = Object.values( - activityLogsEventEnum, - ).filter((index) => index.value === row.getValue("event")); - const bgClass = `bg-${currentEvent[0].tailwindclass}`; - const hoverClass = `hover:bg-${currentEvent[0].tailwindclass}/90`; - const textClass = `text-${currentEvent[0].tailwindclass}-foreground`; - return currentEvent[0] ? ( + cell: ({ row }: { row: Row }) => { + const eventValue = row.getValue("event"); + + const currentEvent = Object.values(activityLogsEventEnum).find( + (event) => event.value === eventValue, + ); + + if (!currentEvent) { + return GetTranslation("activity-logs.event_not_match"); + } + + const bgClass = `bg-${currentEvent.tailwindclass}`; + const hoverClass = `hover:bg-${currentEvent.tailwindclass}/90`; + const textClass = `text-${currentEvent.tailwindclass}-foreground`; + + return ( - {str(useTrans(currentEvent[0].label)).ucFirst().value()} + {str(GetTranslation(currentEvent.label)) + .ucFirst() + .value()} - ) : ( - useTrans("activity-logs.event_not_match") ); }, }, @@ -68,50 +70,62 @@ export default function Index({ header: ({ column }: { column: { id: string } }) => ( ), - cell: ({ - row, - }: { - row: { original: ActivityLogModel; getValue: Function }; - }) => { - if (row.original.user) + cell: ({ row }: { row: Row }) => { + const { + user, + user_id: userId, + is_console: isConsole, + is_anonymous: isAnonymous, + } = row.original; + + if (user) { return ( - {`${row.original.user.first_name} ${row.original.user.last_name}`} + {`${user.first_name} ${user.last_name}`} } tooltipContent={

- {useTrans("activity-logs.access_model", { - params: { - model: useTrans( - "models.classes.user", - ), + {GetTranslation( + "activity-logs.access_model", + { + params: { + model: GetTranslation( + "models.classes.user", + ), + }, }, - })} + )}

} size="sm" variant="secondary" /> ); - if (row.original.is_console) - return useTrans("activity-logs.user_in_console"); - if (row.original.is_anonymous) - return useTrans("activity-logs.user_anonym"); - return useTrans("activity-logs.user_deleted"); + } + + if (isConsole) { + return GetTranslation("activity-logs.user_in_console"); + } + + if (isAnonymous) { + return GetTranslation("activity-logs.user_anonym"); + } + + return GetTranslation("activity-logs.user_deleted"); }, }, { @@ -119,72 +133,89 @@ export default function Index({ header: ({ column }: { column: { id: string } }) => ( ), - cell: ({ - row, - }: { - row: { getValue: Function; original: ActivityLogModel }; - }) => { - const modelSplit = row.getValue("model_class").split("\\"); + cell: ({ row }: { row: Row }) => { + const modelClass = row.getValue("model_class"); + const { event, model_id: modelId } = row.original; + + const modelSplit = modelClass.split("\\"); const modelName = modelSplit[modelSplit.length - 1]; const model = str(modelName).kebabCase().value(); - return row.original.event !== - activityLogsEventEnum.deleted.value ? ( - - - {row.getValue("model_class")} - - } - tooltipContent={ -

- {useTrans("activity-logs.access_model", { - params: { - model: useTrans( - `activity-logs.attributes.model_class`, - ), - }, - })} -

- } - size="sm" - variant="secondary" - /> - ) : ( - - {row.getValue("model_class")} - - ); + + if (event !== activityLogsEventEnum.deleted.value) { + return ( + + + {modelClass} + + } + tooltipContent={ +

+ {GetTranslation( + "activity-logs.access_model", + { + params: { + model: GetTranslation( + "activity-logs.attributes.model_class", + ), + }, + }, + )} +

+ } + size="sm" + variant="secondary" + /> + ); + } + + return {modelClass}; }, }, { accessorKey: "created_at", header: ({ column }: { column: { id: string } }) => ( ), - cell: ({ row }: { row: { getValue: Function } }) => ( - - {useFormatedDate(row.getValue("created_at"))} - - ), + cell: ({ row }: { row: Row }) => { + const createdAt = row.getValue("created_at"); + + return ( + + {GetFormatedDate(createdAt)} + + ); + }, }, ]; +}; + +export default function Index({ + activitylogModels, + searchFields, + activityLogsEventEnum, +}: ActivityLogsIndexProps) { + const actionModels = [Views.VISUALIZATION]; + const columns = getColumns(activityLogsEventEnum); return ( ; type ActivityLogsShowProps = { data: SingleResource; - activityLogsEventEnum: Record< - string, - { value: number; name: string; label: string; tailwindclass: string } - >; + activityLogsEventEnum: ActivityLogsEventEnumProps; }; -export default function Show({ - data, - activityLogsEventEnum, -}: ActivityLogsShowProps) { - const { auth } = usePage().props as unknown as { - auth: AuthProp; - }; - const { viewAny } = auth.policies.activityLogs; - const activityLogModel = data.data as ActivityLogModel; - const columns = [ +const getColumns = ( + activityLogModel: ActivityLogModel, + activityLogsEventEnum: ActivityLogsEventEnumProps, +) => { + return [ { accessorKey: "event", - header: str(useTrans("activity-logs.attributes.event")) + header: str(GetTranslation("activity-logs.attributes.event")) .ucFirst() .value(), cell: ({ value }: { value: number }) => { @@ -53,22 +47,24 @@ export default function Show({ - {str(useTrans(currentEvent[0].label)) + {str(GetTranslation(currentEvent[0].label)) .ucFirst() .value()} ) : ( - useTrans("activity-logs.event_not_match") + GetTranslation("activity-logs.event_not_match") ); } - - {useTrans("back.table_not_known")} - ; + return ( + + {GetTranslation("back.table_not_known")} + + ); }, }, { accessorKey: "user_id", - header: str(useTrans("activity-logs.attributes.user_id")) + header: str(GetTranslation("activity-logs.attributes.user_id")) .ucFirst() .value(), cell: ({ value }: { value: number }) => { @@ -84,13 +80,16 @@ export default function Show({ } tooltipContent={

- {useTrans("activity-logs.access_model", { - params: { - model: useTrans( - "models.classes.user", - ), + {GetTranslation( + "activity-logs.access_model", + { + params: { + model: GetTranslation( + "models.classes.user", + ), + }, }, - })} + )}

} size="sm" @@ -98,15 +97,15 @@ export default function Show({ /> ); if (activityLogModel.is_console) - return useTrans("activity-logs.user_in_console"); + return GetTranslation("activity-logs.user_in_console"); if (activityLogModel.is_anonymous) - return useTrans("activity-logs.user_anonym"); - return useTrans("activity-logs.user_deleted"); + return GetTranslation("activity-logs.user_anonym"); + return GetTranslation("activity-logs.user_deleted"); }, }, { accessorKey: "model_class", - header: str(useTrans("activity-logs.attributes.model_class")) + header: str(GetTranslation("activity-logs.attributes.model_class")) .ucFirst() .value(), cell: ({ value }: { value: string }) => { @@ -128,9 +127,9 @@ export default function Show({ } tooltipContent={

- {useTrans("activity-logs.access_model", { + {GetTranslation("activity-logs.access_model", { params: { - model: useTrans( + model: GetTranslation( "activity-logs.attributes.model_class", ), }, @@ -147,18 +146,20 @@ export default function Show({ }, { accessorKey: "modifications", - header: str(useTrans("activity-logs.attributes.modifications")) + header: str( + GetTranslation("activity-logs.attributes.modifications"), + ) .ucFirst() .value(), cell: ({ value }: { value: object }) => { return value && Object.entries(value).length ? (

    - {Object.entries(value).map((value, index) => { + {Object.entries(value).map(([val, text]) => { return ( -
  • - {value[0]}{" "} +
  • + {val}{" "} - ({value[1]}) + ({text})
  • ); @@ -166,22 +167,34 @@ export default function Show({
) : ( - {useTrans("activity-logs.no_changes")} + {GetTranslation("activity-logs.no_changes")} ); }, }, { accessorKey: "created_at", - header: str(useTrans("validation.attributes.created_at")) + header: str(GetTranslation("validation.attributes.created_at")) .ucFirst() .value(), cell: ({ value }: { value: string }) => ( - {useFormatedDate(value)} + {GetFormatedDate(value)} ), }, ]; +}; +export default function Show({ + data, + activityLogsEventEnum, +}: ActivityLogsShowProps) { + const { auth } = usePage().props as unknown as { + auth: AuthProp; + }; + const { viewAny } = auth.policies.activityLogs; + const activityLogModel = data.data as ActivityLogModel; + const columns = getColumns(activityLogModel, activityLogsEventEnum); + const transBackToList = useTrans("back.crud_back_to_list"); const beforeNode = () => { return ( viewAny && ( @@ -189,9 +202,7 @@ export default function Show({ } - tooltipContent={ -

{useTrans("back.crud_back_to_list")}

- } + tooltipContent={

{transBackToList}

} />
) diff --git a/resources/ts/back/Pages/Companies/Create.tsx b/resources/ts/back/Pages/Companies/Create.tsx index ed81f2d5..7c1b26d1 100644 --- a/resources/ts/back/Pages/Companies/Create.tsx +++ b/resources/ts/back/Pages/Companies/Create.tsx @@ -8,7 +8,8 @@ import Form from "@/back/Pages/Companies/Form"; import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; import useTrans from "@/hooks/use-translations"; import { Actions, Methods } from "@/lib/enums"; -import { AuthProp, CompanyModel, SingleResource } from "@/back/types/global"; +import { AuthProp, SingleResource } from "@/back/types/global"; +import { CompanyModel } from "@/back/types/others"; import str from "@/hooks/use-string"; export default function Create({ diff --git a/resources/ts/back/Pages/Companies/Edit.tsx b/resources/ts/back/Pages/Companies/Edit.tsx index 0742d08d..761592dc 100644 --- a/resources/ts/back/Pages/Companies/Edit.tsx +++ b/resources/ts/back/Pages/Companies/Edit.tsx @@ -9,7 +9,8 @@ import Form from "@/back/Pages/Companies/Form"; import useTrans from "@/hooks/use-translations"; import { Actions, Methods } from "@/lib/enums"; import { Views } from "@/lib/utils"; -import { AuthProp, CompanyModel, SingleResource } from "@/back/types/global"; +import { AuthProp, SingleResource } from "@/back/types/global"; +import { CompanyModel } from "@/back/types/others"; import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; import str from "@/hooks/use-string"; diff --git a/resources/ts/back/Pages/Companies/Form.tsx b/resources/ts/back/Pages/Companies/Form.tsx index 91c31ddf..11e9baf1 100644 --- a/resources/ts/back/Pages/Companies/Form.tsx +++ b/resources/ts/back/Pages/Companies/Form.tsx @@ -11,7 +11,8 @@ import SimpleWysiwyg from "@/back/Components/Inputs/Wysiwyg/SimpleWysiwyg"; import useTrans from "@/hooks/use-translations"; import str from "@/hooks/use-string"; import InputError from "@/back/Components/InputError"; -import { CompanyModel, DynamicFieldTypes } from "@/back/types/global"; +import { DynamicFieldTypes } from "@/back/types/dynamic-pages"; +import { CompanyModel } from "@/back/types/others"; import SubmitButton from "@/back/Components/SubmitButton"; import useCrudSubmit from "@/hooks/use-crud-submit"; import { ModelSelect } from "@/back/Components/Inputs/ModelSelect"; @@ -37,7 +38,7 @@ export default function Form({ name: model?.name, logo: model?.logo ?? "", responsable: model?.responsable ?? "", - url: model?.url ?? "", + url: model?.url ?? null, informations: model?.informations, employees: model?.employees ?? 0, latitude: model?.latitude, @@ -99,7 +100,7 @@ export default function Form({ .ucFirst() .value()} setData={setData} - // @ts-ignore + // @ts-expect-error Types diff between custom reset and useForm reset. reset={reset} value={data.logo} required={false} @@ -145,7 +146,7 @@ export default function Form({ type="url" placeholder="ex: https://..." required={false} - value={data.url} + value={data.url ?? ""} setData={setData} /> { + return [ { accessorKey: "name", header: ({ column }: { column: { id: string } }) => ( (

{str( - useTrans("models.classes.company-activity", { + GetTranslation("models.classes.company-activity", { choice: 1, }), ) @@ -106,9 +85,9 @@ export default function Index({ } tooltipContent={

- {useTrans("back.access_model", { + {GetTranslation("back.access_model", { params: { - model: useTrans( + model: GetTranslation( "models.classes.company-activity", { choice: 1 }, ), @@ -128,7 +107,7 @@ export default function Index({

) : (

- {useTrans("back.table_not_known")} + {GetTranslation("back.table_not_known")}

); }, @@ -138,7 +117,7 @@ export default function Index({ header: () => (

{str( - useTrans("models.classes.company-sector", { + GetTranslation("models.classes.company-sector", { choice: 1, }), ) @@ -168,9 +147,9 @@ export default function Index({ } tooltipContent={

- {useTrans("back.access_model", { + {GetTranslation("back.access_model", { params: { - model: useTrans( + model: GetTranslation( "models.classes.company-sector", { choice: 1 }, ), @@ -190,7 +169,7 @@ export default function Index({

) : (

- {useTrans("back.table_not_known")} + {GetTranslation("back.table_not_known")}

); }, @@ -199,11 +178,11 @@ export default function Index({ accessorKey: "coords", header: () => (

- {str(useTrans("validation.custom.latitude")) + {str(GetTranslation("validation.custom.latitude")) .ucFirst() .value()} / - {str(useTrans("validation.custom.longitude")) + {str(GetTranslation("validation.custom.longitude")) .ucFirst() .value()}

@@ -220,7 +199,7 @@ export default function Index({ accessorKey: "published", header: ({ column }: { column: { id: string } }) => ( )} {str( - useTrans( + GetTranslation( row.getValue("published") ? "back.yes" : "back.no", ), ) @@ -250,7 +229,58 @@ export default function Index({ ), }, + { + accessorKey: "updated_at", + header: ({ column }: { column: { id: string } }) => ( + + ), + cell: ({ row }: { row: Row }) => ( + + {GetFormatedDate(row.getValue("updated_at"))} + + ), + }, ]; +}; + +export default function Index({ + companyModels, + searchFields, + modelName, +}: { + companyModels: Resource; + searchFields: string; + modelName: string; +}) { + const { appEnv, auth } = usePage().props as unknown as { + appEnv: AppEnv; + auth: AuthProp; + }; + const { viewAny, viewTrash, create } = auth.policies.companies; + const { viewAny: viewAnyCompanyActivities } = + auth.policies.companyActivities; + const { viewAny: viewAnyCompanySectors } = auth.policies.companySectors; + const isArchiveRoute = route().current(`back.${modelName}.trash`); + const actionModels = isArchiveRoute + ? [Views.RESTORATION, Views.FORCEDELETION] + : [ + Views.VISUALIZATION, + Views.EDITION, + Views.DUPLICATION, + Views.ARCHIVATION, + ]; + const columns = getColumns( + appEnv, + viewAnyCompanyActivities, + viewAnyCompanySectors, + ); const transModel = useTrans( `models.classes.${str(modelName).singular().value()}`, @@ -262,7 +292,7 @@ export default function Index({ const transTitle = str(isArchiveRoute ? transArchiveTitle : transModel) .ucFirst() .value(); - + const transBackToList = useTrans("back.crud_back_to_list"); const beforeNode = () => { return ( isArchiveRoute && @@ -271,14 +301,28 @@ export default function Index({ } - tooltipContent={ -

{useTrans("back.crud_back_to_list")}

- } + tooltipContent={

{transBackToList}

} />
) ); }; + const transAccessToCompanyActivity = useTrans("back.crud_access_to", { + params: { + target: useTrans("models.classes.company-activity", { choice: 2 }), + }, + }); + const transAccessToCompanySector = useTrans("back.crud_access_to", { + params: { + target: useTrans("models.classes.company-sector", { choice: 2 }), + }, + }); + const transAccessToArchived = useTrans("back.crud_access_to", { + params: { + target: useTrans("back.archived_entries"), + }, + }); + const transAddEntry = useTrans("back.crud_add_entry"); const afterNode = () => { return ( !isArchiveRoute && ( @@ -288,16 +332,7 @@ export default function Index({ link={route(`back.company-activities.index`)} content={} tooltipContent={ -

- {useTrans("back.crud_access_to", { - params: { - target: useTrans( - "models.classes.company-activity", - { choice: 2 }, - ), - }, - })} -

+

{transAccessToCompanyActivity}

} variant="outline" /> @@ -306,18 +341,7 @@ export default function Index({ } - tooltipContent={ -

- {useTrans("back.crud_access_to", { - params: { - target: useTrans( - "models.classes.company-sector", - { choice: 2 }, - ), - }, - })} -

- } + tooltipContent={

{transAccessToCompanySector}

} variant="outline" /> )} @@ -325,17 +349,7 @@ export default function Index({ } - tooltipContent={ -

- {useTrans("back.crud_access_to", { - params: { - target: useTrans( - "back.archived_entries", - ), - }, - })} -

- } + tooltipContent={

{transAccessToArchived}

} variant="outline" /> )} @@ -343,9 +357,7 @@ export default function Index({ } - tooltipContent={ -

{useTrans("back.crud_add_entry")}

- } + tooltipContent={

{transAddEntry}

} /> )}
diff --git a/resources/ts/back/Pages/Companies/Show.tsx b/resources/ts/back/Pages/Companies/Show.tsx index 58b75ff9..9b81dd37 100644 --- a/resources/ts/back/Pages/Companies/Show.tsx +++ b/resources/ts/back/Pages/Companies/Show.tsx @@ -9,19 +9,181 @@ import AuthenticatedLayout from "@/back/Layouts/AuthenticatedLayout"; import DetailsTable from "@/back/Components/Table/DetailsTable"; import ModelActions from "@/back/Components/ModelActions"; import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; -import { Views } from "@/lib/utils"; -import { useFormatedDate } from "@/hooks/use-formated-value"; +import { GetFormatedDate, GetTranslation, Views } from "@/lib/utils"; +import { AppEnv, AuthProp, SingleResource } from "@/back/types/global"; import { - AppEnv, - AuthProp, CompanyActivityModel, CompanyModel, CompanySectorModel, - SingleResource, -} from "@/back/types/global"; +} from "@/back/types/others"; import str from "@/hooks/use-string"; import useTrans from "@/hooks/use-translations"; +const getColumns = (appEnv: AppEnv, auth: AuthProp) => { + return [ + { + accessorKey: "name", + header: str(GetTranslation("validation.attributes.name")) + .ucFirst() + .value(), + cell: ({ value }: { value: string }) => value, + }, + { + accessorKey: "logo", + header: str(GetTranslation("validation.custom.logo")) + .ucFirst() + .value(), + cell: ({ value }: { value: string }) => value, + }, + { + accessorKey: "responsable", + header: str(GetTranslation("validation.custom.responsable")) + .ucFirst() + .value(), + cell: ({ value }: { value: string }) => value, + }, + { + accessorKey: "url", + header: str(GetTranslation("validation.custom.url")) + .ucFirst() + .value(), + cell: ({ value }: { value: string }) => value, + }, + { + accessorKey: "informations", + header: str(GetTranslation("validation.custom.informations")) + .ucFirst() + .value(), + cell: ({ value }: { value: string }) => value, + }, + { + accessorKey: "employees", + header: str(GetTranslation("validation.custom.employees")) + .ucFirst() + .value(), + cell: ({ value }: { value: number }) => value, + }, + { + accessorKey: "latitude", + header: str(GetTranslation("validation.custom.latitude")) + .ucFirst() + .value(), + cell: ({ value }: { value: number }) => value, + }, + { + accessorKey: "longitude", + header: str(GetTranslation("validation.custom.longitude")) + .ucFirst() + .value(), + cell: ({ value }: { value: number }) => value, + }, + { + accessorKey: "company_activity", + header: str( + GetTranslation("models.classes.company-activity", { + choice: 1, + }), + ) + .ucFirst() + .value(), + cell: ({ value }: { value: CompanyActivityModel }) => + auth.policies.companyActivities.viewAny ? ( + + + {value.name[appEnv.locale]} + + } + tooltipContent={ +

+ {GetTranslation("back.access_model", { + params: { + model: GetTranslation( + "models.classes.company-activity", + { choice: 1 }, + ), + }, + })} +

+ } + type="button" + size="default" + variant="secondary" + /> + ) : ( + {value.name[appEnv.locale]} + ), + }, + { + accessorKey: "company_sector", + header: str( + GetTranslation("models.classes.company-sector", { choice: 1 }), + ) + .ucFirst() + .value(), + cell: ({ value }: { value: CompanySectorModel | null }) => + value !== null && + (auth.policies.companySectors.viewAny ? ( + + + {value.name[appEnv.locale]} + + } + tooltipContent={ +

+ {GetTranslation("back.access_model", { + params: { + model: GetTranslation( + "models.classes.company-sector", + { choice: 1 }, + ), + }, + })} +

+ } + type="button" + size="default" + variant="secondary" + /> + ) : ( + {value.name[appEnv.locale]} + )), + }, + { + accessorKey: "created_at", + header: str(GetTranslation("validation.attributes.created_at")) + .ucFirst() + .value(), + cell: ({ value }: { value: string }) => ( + {GetFormatedDate(value)} + ), + }, + { + accessorKey: "updated_at", + header: str(GetTranslation("validation.attributes.updated_at")) + .ucFirst() + .value(), + cell: ({ value }: { value: string }) => ( + {GetFormatedDate(value)} + ), + }, + { + accessorKey: "deleted_at", + header: str(GetTranslation("validation.attributes.deleted_at")) + .ucFirst() + .value(), + cell: ({ value }: { value: string }) => ( + {GetFormatedDate(value)} + ), + }, + ]; +}; + export default function Show({ data, modelName, @@ -44,163 +206,8 @@ export default function Show({ }), }, }); - const columns = [ - { - accessorKey: "name", - header: str(useTrans("validation.attributes.name")) - .ucFirst() - .value(), - cell: ({ value }: { value: string }) => value, - }, - { - accessorKey: "logo", - header: str(useTrans("validation.custom.logo")).ucFirst().value(), - cell: ({ value }: { value: string }) => value, - }, - { - accessorKey: "responsable", - header: str(useTrans("validation.custom.responsable")) - .ucFirst() - .value(), - cell: ({ value }: { value: string }) => value, - }, - { - accessorKey: "url", - header: str(useTrans("validation.custom.url")).ucFirst().value(), - cell: ({ value }: { value: string }) => value, - }, - { - accessorKey: "informations", - header: str(useTrans("validation.custom.informations")) - .ucFirst() - .value(), - cell: ({ value }: { value: string }) => value, - }, - { - accessorKey: "employees", - header: str(useTrans("validation.custom.employees")) - .ucFirst() - .value(), - cell: ({ value }: { value: number }) => value, - }, - { - accessorKey: "latitude", - header: str(useTrans("validation.custom.latitude")) - .ucFirst() - .value(), - cell: ({ value }: { value: number }) => value, - }, - { - accessorKey: "longitude", - header: str(useTrans("validation.custom.longitude")) - .ucFirst() - .value(), - cell: ({ value }: { value: number }) => value, - }, - { - accessorKey: "company_activity", - header: str( - useTrans("models.classes.company-activity", { choice: 1 }), - ) - .ucFirst() - .value(), - cell: ({ value }: { value: CompanyActivityModel }) => - auth.policies.companyActivities.viewAny ? ( - - - {value.name[appEnv.locale]} - - } - tooltipContent={ -

- {useTrans("back.access_model", { - params: { - model: useTrans( - "models.classes.company-activity", - { choice: 1 }, - ), - }, - })} -

- } - type="button" - size="default" - variant="secondary" - /> - ) : ( - {value.name[appEnv.locale]} - ), - }, - { - accessorKey: "company_sector", - header: str( - useTrans("models.classes.company-sector", { choice: 1 }), - ) - .ucFirst() - .value(), - cell: ({ value }: { value: CompanySectorModel | null }) => - value !== null && - (auth.policies.companySectors.viewAny ? ( - - - {value.name[appEnv.locale]} - - } - tooltipContent={ -

- {useTrans("back.access_model", { - params: { - model: useTrans( - "models.classes.company-sector", - { choice: 1 }, - ), - }, - })} -

- } - type="button" - size="default" - variant="secondary" - /> - ) : ( - {value.name[appEnv.locale]} - )), - }, - { - accessorKey: "created_at", - header: str(useTrans("validation.attributes.created_at")) - .ucFirst() - .value(), - cell: ({ value }: { value: string }) => ( - {useFormatedDate(value)} - ), - }, - { - accessorKey: "updated_at", - header: str(useTrans("validation.attributes.updated_at")) - .ucFirst() - .value(), - cell: ({ value }: { value: string }) => ( - {useFormatedDate(value)} - ), - }, - { - accessorKey: "deleted_at", - header: str(useTrans("validation.attributes.deleted_at")) - .ucFirst() - .value(), - cell: ({ value }: { value: string }) => ( - {useFormatedDate(value)} - ), - }, - ]; - + const columns = getColumns(appEnv, auth); + const transBackToList = useTrans("back.crud_back_to_list"); const beforeNode = () => { return ( viewAny && ( @@ -208,9 +215,7 @@ export default function Show({ } - tooltipContent={ -

{useTrans("back.crud_back_to_list")}

- } + tooltipContent={

{transBackToList}

} /> ) diff --git a/resources/ts/back/Pages/CompanyActivities/Create.tsx b/resources/ts/back/Pages/CompanyActivities/Create.tsx index c0bb575c..ad8a6e87 100644 --- a/resources/ts/back/Pages/CompanyActivities/Create.tsx +++ b/resources/ts/back/Pages/CompanyActivities/Create.tsx @@ -8,11 +8,8 @@ import Form from "@/back/Pages/CompanyActivities/Form"; import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; import useTrans from "@/hooks/use-translations"; import { Actions, Methods } from "@/lib/enums"; -import { - AuthProp, - CompanyActivityModel, - SingleResource, -} from "@/back/types/global"; +import { AuthProp, SingleResource } from "@/back/types/global"; +import { CompanyActivityModel } from "@/back/types/others"; import str from "@/hooks/use-string"; export default function Create({ diff --git a/resources/ts/back/Pages/CompanyActivities/Edit.tsx b/resources/ts/back/Pages/CompanyActivities/Edit.tsx index b0047288..b78f0854 100644 --- a/resources/ts/back/Pages/CompanyActivities/Edit.tsx +++ b/resources/ts/back/Pages/CompanyActivities/Edit.tsx @@ -9,11 +9,8 @@ import Form from "@/back/Pages/CompanyActivities/Form"; import useTrans from "@/hooks/use-translations"; import { Actions, Methods } from "@/lib/enums"; import { Views } from "@/lib/utils"; -import { - AuthProp, - CompanyActivityModel, - SingleResource, -} from "@/back/types/global"; +import { AuthProp, SingleResource } from "@/back/types/global"; +import { CompanyActivityModel } from "@/back/types/others"; import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; import str from "@/hooks/use-string"; diff --git a/resources/ts/back/Pages/CompanyActivities/Form.tsx b/resources/ts/back/Pages/CompanyActivities/Form.tsx index d0b5aa46..8b9eb73d 100644 --- a/resources/ts/back/Pages/CompanyActivities/Form.tsx +++ b/resources/ts/back/Pages/CompanyActivities/Form.tsx @@ -10,7 +10,8 @@ import SubmitButton from "@/back/Components/SubmitButton"; import useTrans from "@/hooks/use-translations"; import str from "@/hooks/use-string"; import InputError from "@/back/Components/InputError"; -import { AppEnv, CompanyActivityModel } from "@/back/types/global"; +import { AppEnv } from "@/back/types/global"; +import { CompanyActivityModel } from "@/back/types/others"; import useCrudSubmit from "@/hooks/use-crud-submit"; export default function Form({ diff --git a/resources/ts/back/Pages/CompanyActivities/Index.tsx b/resources/ts/back/Pages/CompanyActivities/Index.tsx index 05207d1a..93f7804f 100644 --- a/resources/ts/back/Pages/CompanyActivities/Index.tsx +++ b/resources/ts/back/Pages/CompanyActivities/Index.tsx @@ -23,54 +23,36 @@ import DataTableSearch from "@/back/Components/Table/DataTableSearch"; import DataTableFilterButton from "@/back/Components/Table/DataTableFilterButton"; import SoftdeleteWarning from "@/back/Components/SoftdeleteWarning"; import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; -import { Views } from "@/lib/utils"; -import { AuthProp, CompanyActivityModel, Resource } from "@/back/types/global"; +import { GetFormatedDate, GetTranslation, Views } from "@/lib/utils"; +import { AuthProp, Resource } from "@/back/types/global"; +import { CompanyActivityModel } from "@/back/types/others"; import str from "@/hooks/use-string"; import useTrans from "@/hooks/use-translations"; -import { renderTranslationCell } from "@/lib/translationCellFallback"; +import RenderTranslationCell from "@/back/Components/Table/RenderTranslationCell"; -export default function Index({ - companyActivityModels, - searchFields, - modelName, -}: { - companyActivityModels: Resource; - searchFields: string; - modelName: string; -}) { - const { auth } = usePage().props as unknown as { - auth: AuthProp; - }; - const { viewAny, viewTrash, create } = auth.policies.companyActivities; - const isArchiveRoute = route().current(`back.${modelName}.trash`); - const actionModels = isArchiveRoute - ? [Views.RESTORATION, Views.FORCEDELETION] - : [ - Views.VISUALIZATION, - Views.EDITION, - Views.DUPLICATION, - Views.ARCHIVATION, - ]; - const columns = [ +const getColumns = () => { + return [ { accessorKey: "name", header: ({ column }: { column: { id: string } }) => ( ), - cell: ({ row }: { row: Row }) => { - return renderTranslationCell(row.getValue("name")); - }, + cell: ({ row }: { row: Row }) => ( + + ), }, { accessorKey: "color", header: ({ column }: { column: { id: string } }) => ( ( ( )} {str( - useTrans( + GetTranslation( row.getValue("published") ? "back.yes" : "back.no", ), ) @@ -142,7 +124,50 @@ export default function Index({ ), }, + { + accessorKey: "updated_at", + header: ({ column }: { column: { id: string } }) => ( + + ), + cell: ({ row }: { row: Row }) => ( + + {GetFormatedDate(row.getValue("updated_at"))} + + ), + }, ]; +}; + +export default function Index({ + companyActivityModels, + searchFields, + modelName, +}: { + companyActivityModels: Resource; + searchFields: string; + modelName: string; +}) { + const { auth } = usePage().props as unknown as { + auth: AuthProp; + }; + const { viewAny, viewTrash, create } = auth.policies.companyActivities; + const isArchiveRoute = route().current(`back.${modelName}.trash`); + const actionModels = isArchiveRoute + ? [Views.RESTORATION, Views.FORCEDELETION] + : [ + Views.VISUALIZATION, + Views.EDITION, + Views.DUPLICATION, + Views.ARCHIVATION, + ]; + const columns = getColumns(); const transModel = useTrans( `models.classes.${str(modelName).singular().value()}`, @@ -154,7 +179,7 @@ export default function Index({ const transTitle = str(isArchiveRoute ? transArchiveTitle : transModel) .ucFirst() .value(); - + const transBackToList = useTrans("back.crud_back_to_list"); const beforeNode = () => { return ( isArchiveRoute && @@ -163,14 +188,18 @@ export default function Index({ } - tooltipContent={ -

{useTrans("back.crud_back_to_list")}

- } + tooltipContent={

{transBackToList}

} /> ) ); }; + const transAccessToArchived = useTrans("back.crud_access_to", { + params: { + target: useTrans("back.archived_entries"), + }, + }); + const transAddEntry = useTrans("back.crud_add_entry"); const afterNode = () => { return ( !isArchiveRoute && ( @@ -179,17 +208,7 @@ export default function Index({ } - tooltipContent={ -

- {useTrans("back.crud_access_to", { - params: { - target: useTrans( - "back.archived_entries", - ), - }, - })} -

- } + tooltipContent={

{transAccessToArchived}

} variant="outline" /> )} @@ -197,9 +216,7 @@ export default function Index({ } - tooltipContent={ -

{useTrans("back.crud_add_entry")}

- } + tooltipContent={

{transAddEntry}

} /> )} diff --git a/resources/ts/back/Pages/CompanyActivities/Show.tsx b/resources/ts/back/Pages/CompanyActivities/Show.tsx index 0da31e7b..d6e280d1 100644 --- a/resources/ts/back/Pages/CompanyActivities/Show.tsx +++ b/resources/ts/back/Pages/CompanyActivities/Show.tsx @@ -1,6 +1,7 @@ // * Packages import { usePage } from "@inertiajs/react"; import { route } from "ziggy-js"; +import { ArrowLeftIcon, CheckIcon, XIcon } from "lucide-react"; // * Components shadcn import { Badge } from "@/components/ui/badge"; // * Other @@ -9,19 +10,86 @@ import DetailsTable from "@/back/Components/Table/DetailsTable"; import ModelActions from "@/back/Components/ModelActions"; import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; import AssociatedCompaniesList from "@/back/Components/AssociatedCompaniesList"; -import { Views } from "@/lib/utils"; -import { useFormatedDate } from "@/hooks/use-formated-value"; -import { - AuthProp, - CompanyActivityModel, - Resource, - SingleResource, - TranslationField, -} from "@/back/types/global"; +import RenderTranslationCell from "@/back/Components/Table/RenderTranslationCell"; +import { GetFormatedDate, GetTranslation, Views } from "@/lib/utils"; +import { AuthProp, Resource, SingleResource } from "@/back/types/global"; +import { TranslationField } from "@/back/types/i18n"; +import { CompanyActivityModel } from "@/back/types/others"; import str from "@/hooks/use-string"; import useTrans from "@/hooks/use-translations"; -import { ArrowLeftIcon, CheckIcon, XIcon } from "lucide-react"; -import { renderTranslationCell } from "@/lib/translationCellFallback"; + +const getColumns = () => { + return [ + { + accessorKey: "name", + header: str(GetTranslation("validation.attributes.name")) + .ucFirst() + .value(), + cell: ({ value }: { value: TranslationField }) => ( + + ), + }, + { + accessorKey: "color", + header: str(GetTranslation("validation.custom.color")) + .ucFirst() + .value(), + cell: ({ value }: { value: string }) => value, + }, + { + accessorKey: "created_at", + header: str(GetTranslation("validation.attributes.created_at")) + .ucFirst() + .value(), + cell: ({ value }: { value: string }) => ( + {GetFormatedDate(value)} + ), + }, + { + accessorKey: "published", + header: str(GetTranslation("validation.custom.published")) + .ucFirst() + .value(), + cell: ({ value }: { value: boolean }) => ( + + {value ? ( + + ) : ( + + )} + {str(GetTranslation(value ? "back.yes" : "back.no")) + .ucFirst() + .value()} + + ), + }, + { + accessorKey: "updated_at", + header: str(GetTranslation("validation.attributes.updated_at")) + .ucFirst() + .value(), + cell: ({ value }: { value: string }) => ( + {GetFormatedDate(value)} + ), + }, + { + accessorKey: "deleted_at", + header: str(GetTranslation("validation.attributes.deleted_at")) + .ucFirst() + .value(), + cell: ({ value }: { value: string }) => ( + {GetFormatedDate(value)} + ), + }, + ]; +}; export default function Show({ data, @@ -46,75 +114,8 @@ export default function Show({ }), }, }); - const columns = [ - { - accessorKey: "name", - header: str(useTrans("validation.attributes.name")) - .ucFirst() - .value(), - cell: ({ value }: { value: TranslationField }) => { - return renderTranslationCell(value); - }, - }, - { - accessorKey: "color", - header: str(useTrans("validation.custom.color")).ucFirst().value(), - cell: ({ value }: { value: string }) => value, - }, - { - accessorKey: "created_at", - header: str(useTrans("validation.attributes.created_at")) - .ucFirst() - .value(), - cell: ({ value }: { value: string }) => ( - {useFormatedDate(value)} - ), - }, - { - accessorKey: "published", - header: str(useTrans("validation.custom.published")) - .ucFirst() - .value(), - cell: ({ value }: { value: boolean }) => ( - - {value ? ( - - ) : ( - - )} - {str(useTrans(value ? "back.yes" : "back.no")) - .ucFirst() - .value()} - - ), - }, - { - accessorKey: "updated_at", - header: str(useTrans("validation.attributes.updated_at")) - .ucFirst() - .value(), - cell: ({ value }: { value: string }) => ( - {useFormatedDate(value)} - ), - }, - { - accessorKey: "deleted_at", - header: str(useTrans("validation.attributes.deleted_at")) - .ucFirst() - .value(), - cell: ({ value }: { value: string }) => ( - {useFormatedDate(value)} - ), - }, - ]; - + const columns = getColumns(); + const transBackToList = useTrans("back.crud_back_to_list"); const beforeNode = () => { return ( viewAny && ( @@ -122,9 +123,7 @@ export default function Show({ } - tooltipContent={ -

{useTrans("back.crud_back_to_list")}

- } + tooltipContent={

{transBackToList}

} /> ) @@ -132,16 +131,7 @@ export default function Show({ }; return ( - + { + return [ + { + accessorKey: "name", + header: ({ column }: { column: { id: string } }) => ( + + ), + cell: ({ row }: { row: Row }) => ( + + ), + }, + { + accessorKey: "companies", + header: ({ column }: { column: { id: string } }) => ( + + ), + cell: ({ row }: { row: Row }) => { + return

{row.original.companies.length}

; + }, + }, + { + accessorKey: "published", + header: ({ column }: { column: { id: string } }) => ( + + ), + cell: ({ row }: { row: Row }) => ( + + {row.getValue("published") ? ( + + ) : ( + + )} + {str( + GetTranslation( + row.getValue("published") ? "back.yes" : "back.no", + ), + ) + .ucFirst() + .value()} + + ), + }, + { + accessorKey: "updated_at", + header: ({ column }: { column: { id: string } }) => ( + + ), + cell: ({ row }: { row: Row }) => ( + + {GetFormatedDate(row.getValue("updated_at"))} + + ), + }, + ]; +}; export default function Index({ companySectorModels, @@ -52,72 +137,7 @@ export default function Index({ Views.DUPLICATION, Views.ARCHIVATION, ]; - const columns = [ - { - accessorKey: "name", - header: ({ column }: { column: { id: string } }) => ( - - ), - cell: ({ row }: { row: Row }) => { - return renderTranslationCell(row.getValue("name")); - }, - }, - { - accessorKey: "companies", - header: ({ column }: { column: { id: string } }) => ( - - ), - cell: ({ row }: { row: Row }) => { - return

{row.original.companies.length}

; - }, - }, - { - accessorKey: "published", - header: ({ column }: { column: { id: string } }) => ( - - ), - cell: ({ row }: { row: Row }) => ( - - {row.getValue("published") ? ( - - ) : ( - - )} - {str( - useTrans( - row.getValue("published") ? "back.yes" : "back.no", - ), - ) - .ucFirst() - .value()} - - ), - }, - ]; + const columns = getColumns(); const transModel = useTrans( `models.classes.${str(modelName).singular().value()}`, @@ -130,6 +150,7 @@ export default function Index({ .ucFirst() .value(); + const transBackToList = useTrans("back.crud_back_to_list"); const beforeNode = () => { return ( isArchiveRoute && @@ -138,14 +159,18 @@ export default function Index({ } - tooltipContent={ -

{useTrans("back.crud_back_to_list")}

- } + tooltipContent={

{transBackToList}

} /> ) ); }; + const transAccessToArchived = useTrans("back.crud_access_to", { + params: { + target: useTrans("back.archived_entries"), + }, + }); + const transAddEntry = useTrans("back.crud_add_entry"); const afterNode = () => { return ( !isArchiveRoute && ( @@ -154,17 +179,7 @@ export default function Index({ } - tooltipContent={ -

- {useTrans("back.crud_access_to", { - params: { - target: useTrans( - "back.archived_entries", - ), - }, - })} -

- } + tooltipContent={

{transAccessToArchived}

} variant="outline" /> )} @@ -172,9 +187,7 @@ export default function Index({ } - tooltipContent={ -

{useTrans("back.crud_add_entry")}

- } + tooltipContent={

{transAddEntry}

} /> )} diff --git a/resources/ts/back/Pages/CompanySectors/Show.tsx b/resources/ts/back/Pages/CompanySectors/Show.tsx index e22937f2..570c8a76 100644 --- a/resources/ts/back/Pages/CompanySectors/Show.tsx +++ b/resources/ts/back/Pages/CompanySectors/Show.tsx @@ -10,18 +10,79 @@ import DetailsTable from "@/back/Components/Table/DetailsTable"; import ModelActions from "@/back/Components/ModelActions"; import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; import AssociatedCompaniesList from "@/back/Components/AssociatedCompaniesList"; -import { Views } from "@/lib/utils"; -import { useFormatedDate } from "@/hooks/use-formated-value"; -import { - AuthProp, - CompanySectorModel, - Resource, - SingleResource, - TranslationField, -} from "@/back/types/global"; +import RenderTranslationCell from "@/back/Components/Table/RenderTranslationCell"; +import { GetFormatedDate, GetTranslation, Views } from "@/lib/utils"; +import { AuthProp, Resource, SingleResource } from "@/back/types/global"; +import { CompanySectorModel } from "@/back/types/others"; +import { TranslationField } from "@/back/types/i18n"; import str from "@/hooks/use-string"; import useTrans from "@/hooks/use-translations"; -import { renderTranslationCell } from "@/lib/translationCellFallback"; + +const getColumns = () => { + return [ + { + accessorKey: "name", + header: str(GetTranslation("validation.attributes.name")) + .ucFirst() + .value(), + cell: ({ value }: { value: TranslationField }) => ( + + ), + }, + { + accessorKey: "published", + header: str(GetTranslation("validation.custom.published")) + .ucFirst() + .value(), + cell: ({ value }: { value: boolean }) => ( + + {value ? ( + + ) : ( + + )} + {str(GetTranslation(value ? "back.yes" : "back.no")) + .ucFirst() + .value()} + + ), + }, + { + accessorKey: "created_at", + header: str(GetTranslation("validation.attributes.created_at")) + .ucFirst() + .value(), + cell: ({ value }: { value: string }) => ( + {GetFormatedDate(value)} + ), + }, + { + accessorKey: "updated_at", + header: str(GetTranslation("validation.attributes.updated_at")) + .ucFirst() + .value(), + cell: ({ value }: { value: string }) => ( + {GetFormatedDate(value)} + ), + }, + { + accessorKey: "deleted_at", + header: str(GetTranslation("validation.attributes.deleted_at")) + .ucFirst() + .value(), + cell: ({ value }: { value: string }) => ( + {GetFormatedDate(value)} + ), + }, + ]; +}; export default function Show({ data, @@ -46,70 +107,8 @@ export default function Show({ }), }, }); - const columns = [ - { - accessorKey: "name", - header: str(useTrans("validation.attributes.name")) - .ucFirst() - .value(), - cell: ({ value }: { value: TranslationField }) => { - return renderTranslationCell(value); - }, - }, - { - accessorKey: "published", - header: str(useTrans("validation.custom.published")) - .ucFirst() - .value(), - cell: ({ value }: { value: boolean }) => ( - - {value ? ( - - ) : ( - - )} - {str(useTrans(value ? "back.yes" : "back.no")) - .ucFirst() - .value()} - - ), - }, - { - accessorKey: "created_at", - header: str(useTrans("validation.attributes.created_at")) - .ucFirst() - .value(), - cell: ({ value }: { value: string }) => ( - {useFormatedDate(value)} - ), - }, - { - accessorKey: "updated_at", - header: str(useTrans("validation.attributes.updated_at")) - .ucFirst() - .value(), - cell: ({ value }: { value: string }) => ( - {useFormatedDate(value)} - ), - }, - { - accessorKey: "deleted_at", - header: str(useTrans("validation.attributes.deleted_at")) - .ucFirst() - .value(), - cell: ({ value }: { value: string }) => ( - {useFormatedDate(value)} - ), - }, - ]; - + const columns = getColumns(); + const transBackToList = useTrans("back.crud_back_to_list"); const beforeNode = () => { return ( viewAny && ( @@ -117,9 +116,7 @@ export default function Show({ } - tooltipContent={ -

{useTrans("back.crud_back_to_list")}

- } + tooltipContent={

{transBackToList}

} /> ) diff --git a/resources/ts/back/Pages/ContactCount/Index.tsx b/resources/ts/back/Pages/ContactCount/Index.tsx index 5e17b894..7b2a5759 100644 --- a/resources/ts/back/Pages/ContactCount/Index.tsx +++ b/resources/ts/back/Pages/ContactCount/Index.tsx @@ -1,27 +1,24 @@ +// * Packages import { Row } from "@tanstack/react-table"; +// * Components shadcn +import { Badge } from "@/components/ui/badge"; // * Other import AuthenticatedLayout from "@/back/Layouts/AuthenticatedLayout"; import DataTable from "@/back/Components/Table/DataTable"; -import { ContactCountModel, LaravelEnum, Resource } from "@/back/types/global"; +import { LaravelEnum, Resource } from "@/back/types/global"; +import { ContactCountModel } from "@/back/types/others"; import str from "@/hooks/use-string"; import useTrans from "@/hooks/use-translations"; +import { GetFormatedDate, GetTranslation } from "@/lib/utils"; -export default function Index({ - contactCountModels, - contactFormType, - modelName, -}: { - contactCountModels: Resource; - contactFormType: LaravelEnum; - modelName: string; -}) { - const columns = [ +const getColumns = (contactFormType: LaravelEnum) => { + return [ { accessorKey: "type", header: ({ column }: { column: { id: string } }) => (

{str( - useTrans( + GetTranslation( `validation.custom.contact_count.${column.id}`, ), ) @@ -33,7 +30,7 @@ export default function Index({ const type = Object.values(contactFormType).find( (item) => item.value === row.getValue("type"), ); - if (!type) return; + if (!type) return ""; return (

{str(type.label).ucFirst().value()} @@ -46,7 +43,7 @@ export default function Index({ header: ({ column }: { column: { id: string } }) => (

{str( - useTrans( + GetTranslation( `validation.custom.contact_count.${column.id}`, ), ) @@ -55,7 +52,34 @@ export default function Index({

), }, + { + accessorKey: "updated_at", + header: ({ column }: { column: { id: string } }) => ( +

+ {str(GetTranslation(`validation.attributes.${column.id}`)) + .ucFirst() + .value()} +

+ ), + cell: ({ row }: { row: Row }) => ( + + {GetFormatedDate(row.getValue("updated_at"))} + + ), + }, ]; +}; + +export default function Index({ + contactCountModels, + contactFormType, + modelName, +}: { + contactCountModels: Resource; + contactFormType: LaravelEnum; + modelName: string; +}) { + const columns = getColumns(contactFormType); const transModel = useTrans( `models.classes.${str(modelName).singular().value()}`, diff --git a/resources/ts/back/Pages/Document/Create.tsx b/resources/ts/back/Pages/Document/Create.tsx index a3414a6b..ff332fa0 100644 --- a/resources/ts/back/Pages/Document/Create.tsx +++ b/resources/ts/back/Pages/Document/Create.tsx @@ -8,7 +8,8 @@ import AuthenticatedLayout from "@/back/Layouts/AuthenticatedLayout"; import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; import useTrans from "@/hooks/use-translations"; import { Actions, Methods } from "@/lib/enums"; -import { AuthProp, DocumentModel, SingleResource } from "@/back/types/global"; +import { AuthProp, SingleResource } from "@/back/types/global"; +import { DocumentModel } from "@/back/types/others"; import Form from "@/back/Pages/Document/Form"; import str from "@/hooks/use-string"; diff --git a/resources/ts/back/Pages/Document/Edit.tsx b/resources/ts/back/Pages/Document/Edit.tsx index 4d45e586..c7686a0e 100644 --- a/resources/ts/back/Pages/Document/Edit.tsx +++ b/resources/ts/back/Pages/Document/Edit.tsx @@ -10,7 +10,8 @@ import useTrans from "@/hooks/use-translations"; import { Actions, Methods } from "@/lib/enums"; import Form from "@/back/Pages/Document/Form"; import { Views } from "@/lib/utils"; -import { AuthProp, DocumentModel, SingleResource } from "@/back/types/global"; +import { AuthProp, SingleResource } from "@/back/types/global"; +import { DocumentModel } from "@/back/types/others"; import str from "@/hooks/use-string"; export default function Edit({ @@ -22,7 +23,11 @@ export default function Edit({ }) { const { viewAny } = (usePage().props.auth as AuthProp).policies.documents; const docsModel = data.data as DocumentModel; - const actionModels = [Views.VISUALIZATION, Views.DELETION]; + const actionModels = [ + Views.VISUALIZATION, + Views.DUPLICATION, + Views.DELETION, + ]; const singularModelName = str(modelName).singular().value(); // Translations. diff --git a/resources/ts/back/Pages/Document/Form.tsx b/resources/ts/back/Pages/Document/Form.tsx index 0bcd8af9..3fe9b21f 100644 --- a/resources/ts/back/Pages/Document/Form.tsx +++ b/resources/ts/back/Pages/Document/Form.tsx @@ -13,12 +13,9 @@ import SubmitButton from "@/back/Components/SubmitButton"; import SimpleSwitch from "@/back/Components/Inputs/SimpleSwitch"; import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; import { ModelSelect } from "@/back/Components/Inputs/ModelSelect"; -import { - AppEnv, - DynamicFieldTypes, - DocumentModel, - LaravelEnum, -} from "@/back/types/global"; +import { AppEnv, LaravelEnum } from "@/back/types/global"; +import { DynamicFieldTypes } from "@/back/types/dynamic-pages"; +import { DocumentModel } from "@/back/types/others"; import useTrans from "@/hooks/use-translations"; import str from "@/hooks/use-string"; import { extractIdsFromDatabasePivotRelation } from "@/lib/mapPivotOptions"; @@ -69,7 +66,7 @@ export default function Form({ action, model, post, - data + data, }); const originalDataPath = @@ -95,6 +92,23 @@ export default function Form({ } }; + //* TRANSLATIONS + const transAssociatedIssuPathLabel = useTrans("back.form_associate_to", { + params: { + attribute: useTrans("validation.custom.issuu_path"), + }, + choice: 1, + }); + const transAssociatedIssuPathPlaceholder = useTrans( + "back.issuu.issuu_path.placeholder", + ); + const transAssociatePathLabel = useTrans("back.form_associate_to", { + params: { + attribute: useTrans("validation.custom.file"), + }, + choice: 1, + }); + const transRefreshOriginal = useTrans("back.refresh_original"); return (
@@ -214,23 +228,12 @@ export default function Form({ {data.is_issuu ? ( } - tooltipContent={str( - useTrans("back.refresh_original"), - ) + tooltipContent={str(transRefreshOriginal) .ucFirst() .value()} /> )} - {(!data.is_issuu && model?.path && originalDataPath) && ( + {!data.is_issuu && model?.path && originalDataPath && ( { + return [ { accessorKey: "name", header: ({ column }: { column: { id: string } }) => (

- {str(useTrans("back.document.no_link")) + {str( + GetTranslation( + "back.document.no_link", + ), + ) .ucFirst() .value()}

)} - {renderTranslationCell( - row.getValue("name") as TranslationField, - )} + ); }, @@ -104,7 +84,7 @@ export default function Index({ header: () => (

{str( - useTrans("models.classes.document-category", { + GetTranslation("models.classes.document-category", { choice: 2, }), ) @@ -135,14 +115,17 @@ export default function Index({ } tooltipContent={

- {useTrans("back.access_model", { - params: { - model: useTrans( - "models.classes.document-category", - { choice: 1 }, - ), + {GetTranslation( + "back.access_model", + { + params: { + model: GetTranslation( + "models.classes.document-category", + { choice: 1 }, + ), + }, }, - })} + )}

} type="button" @@ -158,7 +141,7 @@ export default function Index({ ) : (

- {useTrans("back.table_not_known")} + {GetTranslation("back.table_not_known")}

); }, @@ -167,7 +150,7 @@ export default function Index({ accessorKey: "status", header: ({ column }: { column: { id: string } }) => ( }) => { return ( ); }, @@ -185,7 +168,7 @@ export default function Index({ accessorKey: "published_at", header: ({ column }: { column: { id: string } }) => ( }) => ( - {useFormatedDate(row.getValue("published_at"))} + {GetFormatedDate(row.getValue("published_at"))} ), }, @@ -201,7 +184,9 @@ export default function Index({ accessorKey: "updated_at", header: ({ column }: { column: { id: string } }) => ( }) => ( - {useFormatedDate(row.getValue("updated_at"))} + {GetFormatedDate(row.getValue("updated_at"))} ), }, ]; +}; + +export default function Index({ + documentsModel, + searchFields, + modelName, +}: { + documentsModel: Resource; + searchFields: string; + modelName: string; +}) { + const { auth, appEnv } = usePage().props as unknown as { + auth: AuthProp; + appEnv: AppEnv; + }; + const { create } = auth.policies.documents; + const { viewAny: viewAnyDocumentCategories } = + auth.policies.documentCategories; + const { viewAny: viewAnyDocumentTags } = auth.policies.documentTags; + const actionModels = [Views.VISUALIZATION, Views.EDITION, Views.DELETION]; + const columns = getColumns(appEnv, viewAnyDocumentTags); const transModel = useTrans( `models.classes.${str(modelName).singular().value()}`, { choice: 2 }, ); const transTitle = str(transModel).ucFirst().value(); - + const transAccessToDocumentCategory = useTrans("back.crud_access_to", { + params: { + target: useTrans("models.classes.document-category", { choice: 2 }), + }, + }); + const transAccessToDocumentTag = useTrans("back.crud_access_to", { + params: { + target: useTrans("models.classes.document-tag", { choice: 2 }), + }, + }); + const transAddEntry = useTrans("back.crud_add_entry"); const afterNode = () => { return (
@@ -227,18 +243,7 @@ export default function Index({ } - tooltipContent={ -

- {useTrans("back.crud_access_to", { - params: { - target: useTrans( - "models.classes.document-category", - { choice: 2 }, - ), - }, - })} -

- } + tooltipContent={

{transAccessToDocumentCategory}

} variant="outline" /> )} @@ -246,18 +251,7 @@ export default function Index({ } - tooltipContent={ -

- {useTrans("back.crud_access_to", { - params: { - target: useTrans( - "models.classes.document-tag", - { choice: 2 }, - ), - }, - })} -

- } + tooltipContent={

{transAccessToDocumentTag}

} variant="outline" /> )} @@ -265,9 +259,7 @@ export default function Index({ } - tooltipContent={ -

{useTrans("back.crud_add_entry")}

- } + tooltipContent={

{transAddEntry}

} /> )}
diff --git a/resources/ts/back/Pages/Document/Show.tsx b/resources/ts/back/Pages/Document/Show.tsx index a3f9ebbf..4bea3f62 100644 --- a/resources/ts/back/Pages/Document/Show.tsx +++ b/resources/ts/back/Pages/Document/Show.tsx @@ -8,24 +8,273 @@ import { Badge } from "@/components/ui/badge"; import AuthenticatedLayout from "@/back/Layouts/AuthenticatedLayout"; import DetailsTable from "@/back/Components/Table/DetailsTable"; import ModelActions from "@/back/Components/ModelActions"; -import { Views } from "@/lib/utils"; -import { useFormatedDate } from "@/hooks/use-formated-value"; +import { GetFormatedDate, GetTranslation, Views } from "@/lib/utils"; +import { AppEnv, AuthProp, SingleResource } from "@/back/types/global"; import { - AppEnv, - AuthProp, - DocumentCategoryModel, DocumentModel, + DocumentCategoryModel, DocumentTagModel, - SingleResource, - TranslationField, -} from "@/back/types/global"; +} from "@/back/types/others"; +import { TranslationField } from "@/back/types/i18n"; import str from "@/hooks/use-string"; import useTrans from "@/hooks/use-translations"; import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; -import useLocale from "@/store/use-locale-store"; import DocumentStatusBadge from "@/back/Components/Documents/DocumentStatusBadge"; import YearsEnumeBadge from "@/back/Components/Documents/DocumentExerciceBadge"; +const getColumns = ( + appEnv: AppEnv, + viewAnyDocumentCategories: boolean, + viewAnyDocumentTags: boolean, +) => { + return [ + { + accessorKey: "name", + header: str(GetTranslation("validation.attributes.name")) + .ucFirst() + .value(), + cell: ({ value }: { value: TranslationField }) => + value[appEnv.locale] ? ( + value[appEnv.locale] + ) : ( +

+ {GetTranslation("back.table_not_known")} +

+ ), + }, + { + accessorKey: "path", + header: str(GetTranslation("validation.custom.file")) + .ucFirst() + .value(), + cell: ({ value }: { value: TranslationField }) => + value[appEnv.locale] ? ( +
+ {`${appEnv.url}/${value[appEnv.locale]}`} + + ) : ( +

+ {GetTranslation("back.table_not_known")} +

+ ), + }, + { + accessorKey: "issuu_path", + header: str(GetTranslation("validation.custom.issuu_path")) + .ucFirst() + .value(), + cell: ({ value }: { value: TranslationField }) => + value[appEnv.locale] ? ( + + {value[appEnv.locale]} + + ) : ( +

+ {GetTranslation("back.table_not_known")} +

+ ), + }, + { + accessorKey: "exercice", + header: str( + GetTranslation("back.associated_model", { + params: { + model: GetTranslation( + "models.classes.document-exercice", + { + choice: 2, + }, + ), + }, + choice: 2, + }), + ) + .ucFirst() + .value(), + cell: ({ value }: { value: string }) => { + return ; + }, + }, + { + accessorKey: "status", + header: str(GetTranslation("validation.custom.status")) + .ucFirst() + .value(), + cell: ({ value }: { value: string }) => { + return ; + }, + }, + { + accessorKey: "categories", + header: str( + GetTranslation("models.classes.document-category", { + choice: 2, + }), + ) + .ucFirst() + .value(), + cell: ({ value }: { value?: DocumentCategoryModel[] }) => { + return value !== undefined && value.length > 0 ? ( +
+ {value.map((item) => + viewAnyDocumentCategories ? ( + + + + {item.name[appEnv.locale]} + + + } + tooltipContent={ +

+ {GetTranslation( + "back.access_model", + { + params: { + model: GetTranslation( + "models.classes.document-category", + { choice: 1 }, + ), + }, + }, + )} +

+ } + type="button" + size="default" + variant="secondary" + /> + ) : ( + + {item.name[appEnv.locale]} + + ), + )} +
+ ) : ( +

+ {GetTranslation("back.table_not_known")} +

+ ); + }, + }, + { + accessorKey: "tags", + header: str( + GetTranslation("models.classes.document-tag", { choice: 2 }), + ) + .ucFirst() + .value(), + cell: ({ value }: { value?: DocumentTagModel[] }) => { + return value !== undefined && value.length > 0 ? ( +
+ {value.map((item) => + viewAnyDocumentTags ? ( + + + + {item.name[appEnv.locale]} + + + } + tooltipContent={ +

+ {GetTranslation( + "back.access_model", + { + params: { + model: GetTranslation( + "models.classes.document-tag", + { choice: 1 }, + ), + }, + }, + )} +

+ } + type="button" + size="default" + variant="secondary" + /> + ) : ( + + {item.name[appEnv.locale]} + + ), + )} +
+ ) : ( +

+ {GetTranslation("back.table_not_known")} +

+ ); + }, + }, + { + accessorKey: "created_at", + header: str(GetTranslation("validation.attributes.created_at")) + .ucFirst() + .value(), + cell: ({ value }: { value: string }) => { + return ( + {GetFormatedDate(value)} + ); + }, + }, + { + accessorKey: "published_at", + header: str(GetTranslation("validation.custom.published_at")) + .ucFirst() + .value(), + cell: ({ value }: { value: string }) => ( + + {GetFormatedDate(value, undefined, true)} + + ), + }, + { + accessorKey: "unpublished_at", + header: str(GetTranslation("validation.custom.unpublished_at")) + .ucFirst() + .value(), + cell: ({ value }: { value: string }) => ( + {GetFormatedDate(value)} + ), + }, + { + accessorKey: "updated_at", + header: str(GetTranslation("validation.attributes.updated_at")) + .ucFirst() + .value(), + cell: ({ value }: { value: string }) => ( + {GetFormatedDate(value)} + ), + }, + ]; +}; + export default function Show({ data, modelName, @@ -51,236 +300,13 @@ export default function Show({ }), }, }); - const columns = [ - { - accessorKey: "name", - header: str(useTrans("validation.attributes.name")) - .ucFirst() - .value(), - cell: ({ value }: { value: TranslationField }) => - value[appEnv.locale] ? ( - value[appEnv.locale] - ) : ( -

- {useTrans("back.table_not_known")} -

- ), - }, - { - accessorKey: "path", - header: str(useTrans("validation.custom.file")).ucFirst().value(), - cell: ({ value }: { value: TranslationField }) => - value[appEnv.locale] ? ( - - {`${appEnv.url}/${value[appEnv.locale]}`} - - ) : ( -

- {useTrans("back.table_not_known")} -

- ), - }, - { - accessorKey: "issuu_path", - header: str(useTrans("validation.custom.issuu_path")) - .ucFirst() - .value(), - cell: ({ value }: { value: TranslationField }) => - value[appEnv.locale] ? ( - - {value[appEnv.locale]} - - ) : ( -

- {useTrans("back.table_not_known")} -

- ), - }, - { - accessorKey: "exercice", - header: str( - useTrans("back.associated_model", { - params: { - model: useTrans("models.classes.document-exercice", { - choice: 2, - }), - }, - choice: 2, - }), - ) - .ucFirst() - .value(), - cell: ({ value }: { value: string }) => { - return ; - }, - }, - { - accessorKey: "status", - header: str(useTrans("validation.custom.status")).ucFirst().value(), - cell: ({ value }: { value: string }) => { - return ; - }, - }, - { - accessorKey: "categories", - header: str( - useTrans("models.classes.document-category", { choice: 2 }), - ) - .ucFirst() - .value(), - cell: ({ value }: { value?: DocumentCategoryModel[] }) => { - return value !== undefined && value.length > 0 ? ( -
- {value.map((item) => - viewAnyDocumentCategories ? ( - - - - {item.name[appEnv.locale]} - - - } - tooltipContent={ -

- {useTrans("back.access_model", { - params: { - model: useTrans( - "models.classes.document-category", - { choice: 1 }, - ), - }, - })} -

- } - type="button" - size="default" - variant="secondary" - /> - ) : ( - - {item.name[appEnv.locale]} - - ), - )} -
- ) : ( -

- {useTrans("back.table_not_known")} -

- ); - }, - }, - { - accessorKey: "tags", - header: str(useTrans("models.classes.document-tag", { choice: 2 })) - .ucFirst() - .value(), - cell: ({ value }: { value?: DocumentTagModel[] }) => { - return value !== undefined && value.length > 0 ? ( -
- {value.map((item) => - viewAnyDocumentTags ? ( - - - - {item.name[appEnv.locale]} - - - } - tooltipContent={ -

- {useTrans("back.access_model", { - params: { - model: useTrans( - "models.classes.document-tag", - { choice: 1 }, - ), - }, - })} -

- } - type="button" - size="default" - variant="secondary" - /> - ) : ( - - {item.name[appEnv.locale]} - - ), - )} -
- ) : ( -

- {useTrans("back.table_not_known")} -

- ); - }, - }, - { - accessorKey: "created_at", - header: str(useTrans("validation.attributes.created_at")) - .ucFirst() - .value(), - cell: ({ value }: { value: string }) => { - return ( - {useFormatedDate(value)} - ); - }, - }, - { - accessorKey: "published_at", - header: str(useTrans("validation.custom.published_at")) - .ucFirst() - .value(), - cell: ({ value }: { value: string }) => ( - - {useFormatedDate(value, undefined, true)} - - ), - }, - { - accessorKey: "unpublished_at", - header: str(useTrans("validation.custom.unpublished_at")) - .ucFirst() - .value(), - cell: ({ value }: { value: string }) => ( - {useFormatedDate(value)} - ), - }, - { - accessorKey: "updated_at", - header: str(useTrans("validation.attributes.updated_at")) - .ucFirst() - .value(), - cell: ({ value }: { value: string }) => ( - {useFormatedDate(value)} - ), - }, - ]; + const columns = getColumns( + appEnv, + viewAnyDocumentCategories, + viewAnyDocumentTags, + ); + const transBackToList = useTrans("back.crud_back_to_list"); const beforeNode = () => { return ( viewAny && ( @@ -288,9 +314,7 @@ export default function Show({ } - tooltipContent={ -

{useTrans("back.crud_back_to_list")}

- } + tooltipContent={

{transBackToList}

} /> ) diff --git a/resources/ts/back/Pages/DocumentCategory/Create.tsx b/resources/ts/back/Pages/DocumentCategory/Create.tsx index 00ae9f07..194df98f 100644 --- a/resources/ts/back/Pages/DocumentCategory/Create.tsx +++ b/resources/ts/back/Pages/DocumentCategory/Create.tsx @@ -7,11 +7,8 @@ import AuthenticatedLayout from "@/back/Layouts/AuthenticatedLayout"; import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; import useTrans from "@/hooks/use-translations"; import { Actions, Methods } from "@/lib/enums"; -import { - AuthProp, - DocumentCategoryModel, - SingleResource, -} from "@/back/types/global"; +import { AuthProp, SingleResource } from "@/back/types/global"; +import { DocumentCategoryModel } from "@/back/types/others"; import Form from "@/back/Pages/DocumentCategory/Form"; import str from "@/hooks/use-string"; diff --git a/resources/ts/back/Pages/DocumentCategory/Edit.tsx b/resources/ts/back/Pages/DocumentCategory/Edit.tsx index 9d90e2c8..5b6f42db 100644 --- a/resources/ts/back/Pages/DocumentCategory/Edit.tsx +++ b/resources/ts/back/Pages/DocumentCategory/Edit.tsx @@ -1,6 +1,7 @@ // * Packages import { usePage } from "@inertiajs/react"; import { route } from "ziggy-js"; +import { ArrowLeft } from "lucide-react"; // * Other import AuthenticatedLayout from "@/back/Layouts/AuthenticatedLayout"; import ModelActions from "@/back/Components/ModelActions"; @@ -9,12 +10,8 @@ import useTrans from "@/hooks/use-translations"; import { Actions, Methods } from "@/lib/enums"; import Form from "@/back/Pages/DocumentCategory/Form"; import { Views } from "@/lib/utils"; -import { - AuthProp, - DocumentCategoryModel, - SingleResource, -} from "@/back/types/global"; -import { ArrowLeft } from "lucide-react"; +import { AuthProp, SingleResource } from "@/back/types/global"; +import { DocumentCategoryModel } from "@/back/types/others"; import str from "@/hooks/use-string"; export default function Edit({ diff --git a/resources/ts/back/Pages/DocumentCategory/Form.tsx b/resources/ts/back/Pages/DocumentCategory/Form.tsx index b97e9bc1..5562b933 100644 --- a/resources/ts/back/Pages/DocumentCategory/Form.tsx +++ b/resources/ts/back/Pages/DocumentCategory/Form.tsx @@ -5,7 +5,8 @@ import { BookTextIcon } from "lucide-react"; import FieldsetWrapper from "@/back/Components/FieldsetWrapper"; import InputError from "@/back/Components/InputError"; import SimpleString from "@/back/Components/Inputs/SimpleString"; -import { AppEnv, DocumentCategoryModel } from "@/back/types/global"; +import { AppEnv } from "@/back/types/global"; +import { DocumentCategoryModel } from "@/back/types/others"; import useTrans from "@/hooks/use-translations"; import str from "@/hooks/use-string"; import useCrudSubmit from "@/hooks/use-crud-submit"; diff --git a/resources/ts/back/Pages/DocumentCategory/Index.tsx b/resources/ts/back/Pages/DocumentCategory/Index.tsx index 59ce1b29..eeb4c5a7 100644 --- a/resources/ts/back/Pages/DocumentCategory/Index.tsx +++ b/resources/ts/back/Pages/DocumentCategory/Index.tsx @@ -11,18 +11,79 @@ import DataTable from "@/back/Components/Table/DataTable"; import DataTableSearch from "@/back/Components/Table/DataTableSearch"; import DataTableFilterButton from "@/back/Components/Table/DataTableFilterButton"; import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; +import RenderTranslationCell from "@/back/Components/Table/RenderTranslationCell"; import SoftdeleteWarning from "@/back/Components/SoftdeleteWarning"; -import { Views } from "@/lib/utils"; -import { useFormatedDate } from "@/hooks/use-formated-value"; -import { - AppEnv, - AuthProp, - DocumentCategoryModel, - Resource, -} from "@/back/types/global"; +import { GetFormatedDate, GetTranslation, Views } from "@/lib/utils"; +import { AppEnv, AuthProp, Resource } from "@/back/types/global"; +import { DocumentCategoryModel } from "@/back/types/others"; import str from "@/hooks/use-string"; import useTrans from "@/hooks/use-translations"; -import { renderTranslationCell } from "@/lib/translationCellFallback"; + +const getColumns = () => { + return [ + { + accessorKey: "name", + header: ({ column }: { column: { id: string } }) => ( + + ), + cell: ({ row }: { row: Row }) => ( + + ), + }, + { + accessorKey: "documents", + header: () => ( +

+ {str( + GetTranslation("back.associated_model", { + params: { + model: GetTranslation( + "models.classes.document", + { + choice: 2, + }, + ), + }, + choice: 2, + }), + ) + .ucFirst() + .value()} +

+ ), + cell: ({ row }: { row: Row }) => ( +
+

{row.original.documents_count ?? 0}

+
+ ), + }, + { + accessorKey: "updated_at", + header: ({ column }: { column: { id: string } }) => ( + + ), + cell: ({ row }: { row: Row }) => ( + + {GetFormatedDate(row.getValue("updated_at"))} + + ), + }, + ]; +}; export default function Index({ documentCategoryModel, @@ -47,62 +108,7 @@ export default function Index({ Views.DUPLICATION, Views.ARCHIVATION, ]; - const columns = [ - { - accessorKey: "name", - header: ({ column }: { column: { id: string } }) => ( - - ), - cell: ({ row }: { row: Row }) => { - return renderTranslationCell(row.getValue("name")); - }, - }, - { - accessorKey: "documents", - header: () => ( -

- {str( - useTrans("back.associated_model", { - params: { - model: useTrans("models.classes.document", { - choice: 2, - }), - }, - choice: 2, - }), - ) - .ucFirst() - .value()} -

- ), - cell: ({ row }: { row: Row }) => ( -
-

{row.original.documents_count ?? 0}

-
- ), - }, - { - accessorKey: "updated_at", - header: ({ column }: { column: { id: string } }) => ( - - ), - cell: ({ row }: { row: Row }) => ( - - {useFormatedDate(row.getValue("updated_at"))} - - ), - }, - ]; + const columns = getColumns(); const transModel = useTrans( `models.classes.${str(modelName).singular().value()}`, { choice: 2 }, @@ -114,6 +120,7 @@ export default function Index({ .ucFirst() .value(); + const transBackToList = useTrans("back.crud_back_to_list"); const beforeNode = () => { return ( isArchiveRoute && @@ -122,14 +129,18 @@ export default function Index({ } - tooltipContent={ -

{useTrans("back.crud_back_to_list")}

- } + tooltipContent={

{transBackToList}

} /> ) ); }; + const transAccessToArchived = useTrans("back.crud_access_to", { + params: { + target: useTrans("back.archived_entries"), + }, + }); + const transAddEntry = useTrans("back.crud_add_entry"); const afterNode = () => { return ( !isArchiveRoute && ( @@ -138,17 +149,7 @@ export default function Index({ } - tooltipContent={ -

- {useTrans("back.crud_access_to", { - params: { - target: useTrans( - "back.archived_entries", - ), - }, - })} -

- } + tooltipContent={

{transAccessToArchived}

} variant="outline" /> )} @@ -156,9 +157,7 @@ export default function Index({ } - tooltipContent={ -

{useTrans("back.crud_add_entry")}

- } + tooltipContent={

{transAddEntry}

} /> )} diff --git a/resources/ts/back/Pages/DocumentCategory/Show.tsx b/resources/ts/back/Pages/DocumentCategory/Show.tsx index afbbfe72..cc007510 100644 --- a/resources/ts/back/Pages/DocumentCategory/Show.tsx +++ b/resources/ts/back/Pages/DocumentCategory/Show.tsx @@ -8,21 +8,68 @@ import { Badge } from "@/components/ui/badge"; import AuthenticatedLayout from "@/back/Layouts/AuthenticatedLayout"; import DetailsTable from "@/back/Components/Table/DetailsTable"; import ModelActions from "@/back/Components/ModelActions"; -import { Views } from "@/lib/utils"; -import { useFormatedDate } from "@/hooks/use-formated-value"; +import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; +import AssociatedDocumentList from "@/back/Components/Documents/AssociatedDocumentList"; +import RenderTranslationCell from "@/back/Components/Table/RenderTranslationCell"; +import { GetFormatedDate, GetTranslation, Views } from "@/lib/utils"; import { AppEnv, AuthProp, - DocumentTagModel, Resource, SingleResource, - TranslationField, } from "@/back/types/global"; +import { DocumentTagModel } from "@/back/types/others"; +import { TranslationField } from "@/back/types/i18n"; import str from "@/hooks/use-string"; import useTrans from "@/hooks/use-translations"; -import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; -import AssociatedDocumentList from "@/back/Components/Documents/AssociatedDocumentList"; -import { renderTranslationCell } from "@/lib/translationCellFallback"; + +const getColumns = (appEnv: AppEnv) => { + return [ + { + accessorKey: "name", + header: str(GetTranslation("validation.attributes.name")) + .ucFirst() + .value(), + cell: ({ value }: { value: TranslationField }) => ( + + ), + }, + { + accessorKey: "slug", + header: str(GetTranslation("validation.custom.slug")) + .ucFirst() + .value(), + cell: ({ value }: { value: TranslationField }) => + value[appEnv.locale] ? ( + value[appEnv.locale] + ) : ( +

+ {GetTranslation("back.table_not_known")} +

+ ), + }, + { + accessorKey: "created_at", + header: str(GetTranslation("validation.attributes.created_at")) + .ucFirst() + .value(), + cell: ({ value }: { value: string }) => { + return ( + {GetFormatedDate(value)} + ); + }, + }, + { + accessorKey: "updated_at", + header: str(GetTranslation("validation.attributes.updated_at")) + .ucFirst() + .value(), + cell: ({ value }: { value: string }) => ( + {GetFormatedDate(value)} + ), + }, + ]; +}; export default function Show({ data, @@ -48,50 +95,9 @@ export default function Show({ }), }, }); - const columns = [ - { - accessorKey: "name", - header: str(useTrans("validation.attributes.name")) - .ucFirst() - .value(), - cell: ({ value }: { value: TranslationField }) => { - return renderTranslationCell(value); - }, - }, - { - accessorKey: "slug", - header: str(useTrans("validation.custom.slug")).ucFirst().value(), - cell: ({ value }: { value: TranslationField }) => - value[appEnv.locale] ? ( - value[appEnv.locale] - ) : ( -

- {useTrans("back.table_not_known")} -

- ), - }, - { - accessorKey: "created_at", - header: str(useTrans("validation.attributes.created_at")) - .ucFirst() - .value(), - cell: ({ value }: { value: string }) => { - return ( - {useFormatedDate(value)} - ); - }, - }, - { - accessorKey: "updated_at", - header: str(useTrans("validation.attributes.updated_at")) - .ucFirst() - .value(), - cell: ({ value }: { value: string }) => ( - {useFormatedDate(value)} - ), - }, - ]; + const columns = getColumns(appEnv); + const transBackToList = useTrans("back.crud_back_to_list"); const beforeNode = () => { return ( viewAny && ( @@ -99,9 +105,7 @@ export default function Show({ } - tooltipContent={ -

{useTrans("back.crud_back_to_list")}

- } + tooltipContent={

{transBackToList}

} /> ) diff --git a/resources/ts/back/Pages/DocumentTag/Create.tsx b/resources/ts/back/Pages/DocumentTag/Create.tsx index 53ca536f..340ada05 100644 --- a/resources/ts/back/Pages/DocumentTag/Create.tsx +++ b/resources/ts/back/Pages/DocumentTag/Create.tsx @@ -7,11 +7,8 @@ import AuthenticatedLayout from "@/back/Layouts/AuthenticatedLayout"; import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; import useTrans from "@/hooks/use-translations"; import { Actions, Methods } from "@/lib/enums"; -import { - AuthProp, - DocumentTagModel, - SingleResource, -} from "@/back/types/global"; +import { AuthProp, SingleResource } from "@/back/types/global"; +import { DocumentTagModel } from "@/back/types/others"; import Form from "@/back/Pages/DocumentTag/Form"; import str from "@/hooks/use-string"; diff --git a/resources/ts/back/Pages/DocumentTag/Edit.tsx b/resources/ts/back/Pages/DocumentTag/Edit.tsx index ecb09721..79d309e2 100644 --- a/resources/ts/back/Pages/DocumentTag/Edit.tsx +++ b/resources/ts/back/Pages/DocumentTag/Edit.tsx @@ -10,11 +10,8 @@ import useTrans from "@/hooks/use-translations"; import { Actions, Methods } from "@/lib/enums"; import Form from "@/back/Pages/DocumentTag/Form"; import { Views } from "@/lib/utils"; -import { - AuthProp, - DocumentTagModel, - SingleResource, -} from "@/back/types/global"; +import { AuthProp, SingleResource } from "@/back/types/global"; +import { DocumentTagModel } from "@/back/types/others"; import str from "@/hooks/use-string"; export default function Edit({ diff --git a/resources/ts/back/Pages/DocumentTag/Form.tsx b/resources/ts/back/Pages/DocumentTag/Form.tsx index 7c51cf1d..f8ee3186 100644 --- a/resources/ts/back/Pages/DocumentTag/Form.tsx +++ b/resources/ts/back/Pages/DocumentTag/Form.tsx @@ -5,8 +5,8 @@ import { BookTextIcon } from "lucide-react"; import FieldsetWrapper from "@/back/Components/FieldsetWrapper"; import InputError from "@/back/Components/InputError"; import SimpleString from "@/back/Components/Inputs/SimpleString"; -import { Methods } from "@/lib/enums"; -import { AppEnv, DocumentTagModel } from "@/back/types/global"; +import { AppEnv } from "@/back/types/global"; +import { DocumentTagModel } from "@/back/types/others"; import useTrans from "@/hooks/use-translations"; import str from "@/hooks/use-string"; import useCrudSubmit from "@/hooks/use-crud-submit"; @@ -43,7 +43,7 @@ export default function Form({ action, model, post, - data + data, }); return ( diff --git a/resources/ts/back/Pages/DocumentTag/Index.tsx b/resources/ts/back/Pages/DocumentTag/Index.tsx index bc139412..8687b368 100644 --- a/resources/ts/back/Pages/DocumentTag/Index.tsx +++ b/resources/ts/back/Pages/DocumentTag/Index.tsx @@ -16,62 +16,37 @@ import DataTable from "@/back/Components/Table/DataTable"; import DataTableSearch from "@/back/Components/Table/DataTableSearch"; import DataTableFilterButton from "@/back/Components/Table/DataTableFilterButton"; import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; +import RenderTranslationCell from "@/back/Components/Table/RenderTranslationCell"; import SoftdeleteWarning from "@/back/Components/SoftdeleteWarning"; -import { Views } from "@/lib/utils"; -import { useFormatedDate } from "@/hooks/use-formated-value"; -import { - AppEnv, - AuthProp, - DocumentTagModel, - Resource, -} from "@/back/types/global"; +import { GetFormatedDate, GetTranslation, Views } from "@/lib/utils"; +import { AppEnv, AuthProp, Resource } from "@/back/types/global"; +import { DocumentTagModel } from "@/back/types/others"; import str from "@/hooks/use-string"; import useTrans from "@/hooks/use-translations"; -import { renderTranslationCell } from "@/lib/translationCellFallback"; -export default function Index({ - documentTagModel, - searchFields, - modelName, -}: { - documentTagModel: Resource; - searchFields: string; - modelName: string; -}) { - const { auth } = usePage().props as unknown as { - auth: AuthProp; - appEnv: AppEnv; - }; - const { viewAny, viewTrash, create } = auth.policies.documentTags; - const isArchiveRoute = route().current(`back.${modelName}.trash`); - const actionModels = isArchiveRoute - ? [Views.RESTORATION, Views.FORCEDELETION] - : [ - Views.VISUALIZATION, - Views.EDITION, - Views.DUPLICATION, - Views.ARCHIVATION, - ]; - const columns = [ +const getColumns = () => { + return [ { accessorKey: "name", header: ({ column }: { column: { id: string } }) => ( ), - cell: ({ row }: { row: Row }) => { - return renderTranslationCell(row.getValue("name")); - }, + cell: ({ row }: { row: Row }) => ( + + ), }, { accessorKey: "color", header: ({ column }: { column: { id: string } }) => ( (

{str( - useTrans("back.associated_model", { + GetTranslation("back.associated_model", { params: { - model: useTrans("models.classes.document", { - choice: 2, - }), + model: GetTranslation( + "models.classes.document", + { + choice: 2, + }, + ), }, choice: 2, }), @@ -121,7 +99,9 @@ export default function Index({ accessorKey: "updated_at", header: ({ column }: { column: { id: string } }) => ( }) => ( - {useFormatedDate(row.getValue("updated_at"))} + {GetFormatedDate(row.getValue("updated_at"))} ), }, ]; +}; + +export default function Index({ + documentTagModel, + searchFields, + modelName, +}: { + documentTagModel: Resource; + searchFields: string; + modelName: string; +}) { + const { auth } = usePage().props as unknown as { + auth: AuthProp; + appEnv: AppEnv; + }; + const { viewAny, viewTrash, create } = auth.policies.documentTags; + const isArchiveRoute = route().current(`back.${modelName}.trash`); + const actionModels = isArchiveRoute + ? [Views.RESTORATION, Views.FORCEDELETION] + : [ + Views.VISUALIZATION, + Views.EDITION, + Views.DUPLICATION, + Views.ARCHIVATION, + ]; + const columns = getColumns(); const transModel = useTrans( `models.classes.${str(modelName).singular().value()}`, { choice: 2 }, @@ -145,6 +151,7 @@ export default function Index({ .ucFirst() .value(); + const transBackToList = useTrans("back.crud_back_to_list"); const beforeNode = () => { return ( isArchiveRoute && @@ -153,15 +160,19 @@ export default function Index({ } - tooltipContent={ -

{useTrans("back.crud_back_to_list")}

- } + tooltipContent={

{transBackToList}

} /> ) ); }; + const transAccessToArchived = useTrans("back.crud_access_to", { + params: { + target: useTrans("back.archived_entries"), + }, + }); + const transAddEntry = useTrans("back.crud_add_entry"); const afterNode = () => { return ( !isArchiveRoute && ( @@ -170,17 +181,7 @@ export default function Index({ } - tooltipContent={ -

- {useTrans("back.crud_access_to", { - params: { - target: useTrans( - "back.archived_entries", - ), - }, - })} -

- } + tooltipContent={

{transAccessToArchived}

} variant="outline" /> )} @@ -188,9 +189,7 @@ export default function Index({ } - tooltipContent={ -

{useTrans("back.crud_add_entry")}

- } + tooltipContent={

{transAddEntry}

} /> )} diff --git a/resources/ts/back/Pages/DocumentTag/Show.tsx b/resources/ts/back/Pages/DocumentTag/Show.tsx index 8322dc62..a0db37e5 100644 --- a/resources/ts/back/Pages/DocumentTag/Show.tsx +++ b/resources/ts/back/Pages/DocumentTag/Show.tsx @@ -1,6 +1,7 @@ // * Packages import { usePage } from "@inertiajs/react"; import { route } from "ziggy-js"; +import { ArrowLeft } from "lucide-react"; // * Components shadcn import { Badge } from "@/components/ui/badge"; import { @@ -12,22 +13,89 @@ import { import AuthenticatedLayout from "@/back/Layouts/AuthenticatedLayout"; import DetailsTable from "@/back/Components/Table/DetailsTable"; import ModelActions from "@/back/Components/ModelActions"; -import { Views } from "@/lib/utils"; -import { useFormatedDate } from "@/hooks/use-formated-value"; +import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; +import AssociatedDocumentList from "@/back/Components/Documents/AssociatedDocumentList"; +import RenderTranslationCell from "@/back/Components/Table/RenderTranslationCell"; +import { GetFormatedDate, GetTranslation, Views } from "@/lib/utils"; import { AppEnv, AuthProp, - DocumentTagModel, Resource, SingleResource, - TranslationField, } from "@/back/types/global"; +import { DocumentTagModel } from "@/back/types/others"; +import { TranslationField } from "@/back/types/i18n"; import str from "@/hooks/use-string"; import useTrans from "@/hooks/use-translations"; -import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; -import { ArrowLeft } from "lucide-react"; -import AssociatedDocumentList from "@/back/Components/Documents/AssociatedDocumentList"; -import { renderTranslationCell } from "@/lib/translationCellFallback"; + +const getColumns = (appEnv: AppEnv) => { + return [ + { + accessorKey: "name", + header: str(GetTranslation("validation.attributes.name")) + .ucFirst() + .value(), + cell: ({ value }: { value: TranslationField }) => ( + + ), + }, + { + accessorKey: "slug", + header: str(GetTranslation("validation.custom.slug")) + .ucFirst() + .value(), + cell: ({ value }: { value: TranslationField }) => + value[appEnv.locale] ? ( + value[appEnv.locale] + ) : ( +

+ {GetTranslation("back.table_not_known")} +

+ ), + }, + { + accessorKey: "color", + header: str(GetTranslation("validation.custom.color")) + .ucFirst() + .value(), + cell: ({ value }: { value: string }) => { + return ( + + +
+ + {value} + + ); + }, + }, + { + accessorKey: "created_at", + header: str(GetTranslation("validation.attributes.created_at")) + .ucFirst() + .value(), + cell: ({ value }: { value: string }) => { + return ( + {GetFormatedDate(value)} + ); + }, + }, + { + accessorKey: "updated_at", + header: str(GetTranslation("validation.attributes.updated_at")) + .ucFirst() + .value(), + cell: ({ value }: { value: string }) => ( + {GetFormatedDate(value)} + ), + }, + ]; +}; export default function Show({ data, @@ -53,69 +121,9 @@ export default function Show({ }), }, }); - const columns = [ - { - accessorKey: "name", - header: str(useTrans("validation.attributes.name")) - .ucFirst() - .value(), - cell: ({ value }: { value: TranslationField }) => { - return renderTranslationCell(value); - }, - }, - { - accessorKey: "slug", - header: str(useTrans("validation.custom.slug")).ucFirst().value(), - cell: ({ value }: { value: TranslationField }) => - value[appEnv.locale] ? ( - value[appEnv.locale] - ) : ( -

- {useTrans("back.table_not_known")} -

- ), - }, - { - accessorKey: "color", - header: str(useTrans("validation.custom.color")).ucFirst().value(), - cell: ({ value }: { value: string }) => { - return ( - - -
- - {value} - - ); - }, - }, - { - accessorKey: "created_at", - header: str(useTrans("validation.attributes.created_at")) - .ucFirst() - .value(), - cell: ({ value }: { value: string }) => { - return ( - {useFormatedDate(value)} - ); - }, - }, - { - accessorKey: "updated_at", - header: str(useTrans("validation.attributes.updated_at")) - .ucFirst() - .value(), - cell: ({ value }: { value: string }) => ( - {useFormatedDate(value)} - ), - }, - ]; + const columns = getColumns(appEnv); + const transBackToList = useTrans("back.crud_back_to_list"); const beforeNode = () => { return ( viewAny && ( @@ -123,9 +131,7 @@ export default function Show({ } - tooltipContent={ -

{useTrans("back.crud_back_to_list")}

- } + tooltipContent={

{transBackToList}

} />
) diff --git a/resources/ts/back/Pages/DynamicPages/Create.tsx b/resources/ts/back/Pages/DynamicPages/Create.tsx index 614a0814..33e11f7a 100644 --- a/resources/ts/back/Pages/DynamicPages/Create.tsx +++ b/resources/ts/back/Pages/DynamicPages/Create.tsx @@ -8,11 +8,8 @@ import Form from "@/back/Pages/DynamicPages/Form"; import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; import useTrans from "@/hooks/use-translations"; import { Actions, Methods } from "@/lib/enums"; -import { - AuthProp, - DynamicPageModel, - SingleResource, -} from "@/back/types/global"; +import { AuthProp, SingleResource } from "@/back/types/global"; +import { DynamicPageModel } from "@/back/types/dynamic-pages"; import str from "@/hooks/use-string"; export default function Create({ diff --git a/resources/ts/back/Pages/DynamicPages/Edit.tsx b/resources/ts/back/Pages/DynamicPages/Edit.tsx index 1dc9cce0..97e5b57c 100644 --- a/resources/ts/back/Pages/DynamicPages/Edit.tsx +++ b/resources/ts/back/Pages/DynamicPages/Edit.tsx @@ -10,11 +10,8 @@ import Form from "@/back/Pages/DynamicPages/Form"; import useTrans from "@/hooks/use-translations"; import { Actions, Methods } from "@/lib/enums"; import { Views } from "@/lib/utils"; -import { - AuthProp, - SingleResource, - DynamicPageModel, -} from "@/back/types/global"; +import { AuthProp, SingleResource } from "@/back/types/global"; +import { DynamicPageModel } from "@/back/types/dynamic-pages"; import str from "@/hooks/use-string"; export default function Edit({ diff --git a/resources/ts/back/Pages/DynamicPages/Form.tsx b/resources/ts/back/Pages/DynamicPages/Form.tsx index 3e05605e..9d5a3bec 100644 --- a/resources/ts/back/Pages/DynamicPages/Form.tsx +++ b/resources/ts/back/Pages/DynamicPages/Form.tsx @@ -34,14 +34,12 @@ import GoToFrontView from "@/back/Components/GoToFrontView"; import useTrans from "@/hooks/use-translations"; import InputError from "@/back/Components/InputError"; import str from "@/hooks/use-string"; +import { AppEnv, AuthProp, LaravelEnum } from "@/back/types/global"; import { - AppEnv, - AuthProp, ConfigField, DynamicPageModel, - LaravelEnum, PageConfigModel, -} from "@/back/types/global"; +} from "@/back/types/dynamic-pages"; import SubmitButton from "@/back/Components/SubmitButton"; import useCrudSubmit from "@/hooks/use-crud-submit"; import Slugifycator from "@/back/Components/Inputs/Slugifycator"; @@ -75,9 +73,9 @@ export default function Form({ .pageConfigModels as Array; // Models filtered based on the presence of the locale. const filteredPageConfigModels = pageConfigModels.filter( - (model) => - model.label?.[locale] !== undefined && - model.config?.[locale] !== undefined, + (pageConfigModel) => + pageConfigModel.label?.[locale] !== undefined && + pageConfigModel.config?.[locale] !== undefined, ); // Set empty config by default. @@ -121,8 +119,24 @@ export default function Form({ model: useTrans("models.classes.page-config", { choice: 1 }), }, }); + const transValidationRootLabel = useTrans("validation.custom.root"); + const transValidationParentPageLabel = useTrans("back.form_associate_to", { + params: { + attribute: useTrans("validation.custom.parent_page"), + }, + choice: 2, + }); + const transAssociateToStatus = useTrans("back.form_associate_to", { + params: { + attribute: useTrans("validation.custom.status"), + }, + choice: 1, + }); + const transFormTitleIsLocked = useTrans("back.form_title_isLocked"); + const transFormSubtitleIsLocked = useTrans("back.form_subtitle_isLocked"); + const getConfig = (id: number) => - configOptions.find((objet) => objet.id == id)?.config[ + configOptions.find((objet) => objet.id === id)?.config[ locale ] as ConfigField[]; @@ -137,7 +151,8 @@ export default function Form({ isLocked: model?.isLocked ?? false, isRoot: model?.isRoot ?? false, }); - + const defaultIsRootValue = + !data.isRoot && data.page_id ? [data.page_id] : []; const submit = useCrudSubmit({ method, modelRoute, @@ -149,27 +164,31 @@ export default function Form({ }); function getFullSlugPath( - models: DynamicPageModel[], - selectedGroup: string | null, - locale: string = "fr", + targetModels: DynamicPageModel[], + currentSelectedGroup: string | null, + currentLocale: string = "fr", ): string | null { - if (!selectedGroup) return null; + if (!currentSelectedGroup) return null; // Trouver le modèle sélectionné à partir de son title - const current = models.find( - (m) => - m.title[locale]?.toLowerCase() === selectedGroup.toLowerCase(), + const current = targetModels.find( + (targetModel) => + targetModel.title[currentLocale]?.toLowerCase() === + currentSelectedGroup.toLowerCase(), ); if (!current) return null; // Fonction récursive pour reconstruire le chemin - const buildPath = (model: DynamicPageModel): string => { - const parent = model.page_id - ? models.find((m) => m.id === model.page_id) + const buildPath = (modelParent: DynamicPageModel): string => { + const parent = modelParent.page_id + ? targetModels.find( + (targetModel) => targetModel.id === modelParent.page_id, + ) : null; - const currentTranslatedModelSlug = model.slug[locale] ?? ""; + const currentTranslatedModelSlug = + modelParent.slug[currentLocale] ?? ""; if (!parent) return currentTranslatedModelSlug; @@ -211,18 +230,7 @@ export default function Form({ @@ -261,26 +269,11 @@ export default function Form({ multiple={false} field="title" closeOnSelect - label={str( - useTrans("back.form_associate_to", { - params: { - attribute: useTrans( - "validation.custom.parent_page", - ), - }, - choice: 2, - }), - ) + label={str(transValidationParentPageLabel) .ucFirst() .value()} placeholder="ex: " - defaultValues={ - !data.isRoot - ? data.page_id - ? [data.page_id] - : [] - : [] - } + defaultValues={defaultIsRootValue} exclude={model?.id} required={false} setData={setData} @@ -430,7 +423,7 @@ export default function Form({ : null } setData={setData} - // @ts-ignore + // @ts-expect-error Types diff between custom reset and useForm reset. reset={reset} /> )} @@ -438,9 +431,9 @@ export default function Form({ {isConceptor && ( } - description={useTrans("back.form_subtitle_isLocked")} + description={transFormSubtitleIsLocked} inline > { + return [ { accessorKey: "title", header: ({ column }: { column: { id: string } }) => ( }) => (
- {renderTranslationCell( - row.getValue("title") as TranslationField, - )} + {row.original.isRoot && ( - {useTrans("back.page_root")} + {GetTranslation("back.page_root")} )} @@ -99,7 +73,7 @@ export default function Index({ accessorKey: "parent_page", header: ({ column }: { column: { id: string } }) => ( - {useTrans("back.access_model", { + {GetTranslation("back.access_model", { params: { - model: useTrans( + model: GetTranslation( "models.classes.dynamic-page", { choice: 1 }, ), @@ -155,7 +129,7 @@ export default function Index({ /> - {useTrans("back.group_archived")} + {GetTranslation("back.group_archived")}
@@ -165,7 +139,7 @@ export default function Index({ } return (

- {useTrans("back.table_not_known")} + {GetTranslation("back.table_not_known")}

); }, @@ -174,7 +148,7 @@ export default function Index({ accessorKey: "config", header: ({ column }: { column: { id: string } }) => ( - {useTrans("back.access_model", { + {GetTranslation("back.access_model", { params: { - model: useTrans( + model: GetTranslation( "models.classes.page-config", { choice: 1 }, ), @@ -230,7 +204,7 @@ export default function Index({ />
- {useTrans("back.config_archived")} + {GetTranslation("back.config_archived")}
@@ -240,7 +214,7 @@ export default function Index({ } return (

- {useTrans("back.table_not_known")} + {GetTranslation("back.table_not_known")}

); }, @@ -249,7 +223,7 @@ export default function Index({ accessorKey: "status", header: ({ column }: { column: { id: string } }) => (
- {useTrans("back.page_locked")} + {GetTranslation("back.page_locked")}
)} @@ -275,7 +249,9 @@ export default function Index({ accessorKey: "updated_at", header: ({ column }: { column: { id: string } }) => ( }) => ( - {useFormatedDate(row.getValue("updated_at"))} + {GetFormatedDate(row.getValue("updated_at"))} ), }, ]; +}; + +export default function Index({ + dynamicPageModels, + searchFields, + modelName, +}: { + dynamicPageModels: Resource; + searchFields: string; + modelName: string; +}) { + const { auth, appEnv } = usePage().props as unknown as { + auth: AuthProp; + appEnv: AppEnv; + }; + const { viewAny, viewTrash, create } = auth.policies.dynamicPages; + const { viewAny: viewAnyPageConfigs } = auth.policies.pageConfigs; + const isArchiveRoute = route().current(`back.${modelName}.trash`); + const actionModels = isArchiveRoute + ? [Views.RESTORATION, Views.FORCEDELETION] + : [ + Views.VISUALIZATION, + Views.EDITION, + Views.DUPLICATION, + Views.ARCHIVATION, + ]; + + const columns = getColumns(appEnv, modelName, viewAny, viewAnyPageConfigs); const transModel = useTrans( `models.classes.${str(modelName).singular().value()}`, @@ -299,7 +303,7 @@ export default function Index({ const transTitle = str(isArchiveRoute ? transArchiveTitle : transModel) .ucFirst() .value(); - + const transBackToList = useTrans("back.crud_back_to_list"); const beforeNode = () => { return ( isArchiveRoute && @@ -308,14 +312,18 @@ export default function Index({ } - tooltipContent={ -

{useTrans("back.crud_back_to_list")}

- } + tooltipContent={

{transBackToList}

} /> ) ); }; + const transAccessToArchived = useTrans("back.crud_access_to", { + params: { + target: useTrans("back.archived_entries"), + }, + }); + const transAddEntry = useTrans("back.crud_add_entry"); const afterNode = () => { return ( !isArchiveRoute && ( @@ -324,17 +332,7 @@ export default function Index({ } - tooltipContent={ -

- {useTrans("back.crud_access_to", { - params: { - target: useTrans( - "back.archived_entries", - ), - }, - })} -

- } + tooltipContent={

{transAccessToArchived}

} variant="outline" /> )} @@ -342,9 +340,7 @@ export default function Index({ } - tooltipContent={ -

{useTrans("back.crud_add_entry")}

- } + tooltipContent={

{transAddEntry}

} /> )} diff --git a/resources/ts/back/Pages/DynamicPages/Show.tsx b/resources/ts/back/Pages/DynamicPages/Show.tsx index c8d0a876..7308a485 100644 --- a/resources/ts/back/Pages/DynamicPages/Show.tsx +++ b/resources/ts/back/Pages/DynamicPages/Show.tsx @@ -16,50 +16,40 @@ import DetailsTable from "@/back/Components/Table/DetailsTable"; import ModelActions from "@/back/Components/ModelActions"; import DynamicStatusBadge from "@/back/Components/DynamicPages/DynamicStatusBadge"; import RecursiveContent from "@/back/Components/DynamicPages/DynamicPageDetails"; -import { Views } from "@/lib/utils"; -import { useFormatedDate } from "@/hooks/use-formated-value"; +import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; +import RenderTranslationCell from "@/back/Components/Table/RenderTranslationCell"; +import { GetFormatedDate, GetTranslation, Views } from "@/lib/utils"; +import { AppEnv, AuthProp, SingleResource } from "@/back/types/global"; import { - AppEnv, - AuthProp, - DynamicPageModel, + DynamicPageModelWithRelations, PageConfigModel, - SingleResource, - TranslationField, -} from "@/back/types/global"; +} from "@/back/types/dynamic-pages"; +import { TranslationField } from "@/back/types/i18n"; import str from "@/hooks/use-string"; import useTrans from "@/hooks/use-translations"; -import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; -import { renderTranslationCell } from "@/lib/translationCellFallback"; -export default function Show({ - data, - modelName, -}: { - data: SingleResource; - modelName: string; -}) { - const { auth, appEnv } = usePage().props as unknown as { - auth: AuthProp; - appEnv: AppEnv; - }; - const { viewAny } = auth.policies.dynamicPages; - const dynamicPageModel = data.data as DynamicPageModel; - const actionModels = [Views.EDITION, Views.DUPLICATION, Views.ARCHIVATION]; - const columns = [ +const getColumns = ( + appEnv: AppEnv, + auth: AuthProp, + modelName: string, + dynamicPageModel: DynamicPageModelWithRelations, +) => { + return [ { accessorKey: "title", - header: str(useTrans("validation.attributes.title")) + header: str(GetTranslation("validation.attributes.title")) .ucFirst() .value(), - cell: ({ value }: { value: TranslationField }) => - renderTranslationCell(value), + cell: ({ value }: { value: TranslationField }) => ( + + ), }, { accessorKey: "parent_page", - header: str(useTrans("validation.custom.parent_page")) + header: str(GetTranslation("validation.custom.parent_page")) .ucFirst() .value(), - cell: ({ value }: { value: DynamicPageModel }) => + cell: ({ value }: { value: DynamicPageModelWithRelations }) => auth.policies.dynamicPages.viewAny ? ( - {useTrans("back.access_model", { + {GetTranslation("back.access_model", { params: { - model: useTrans( + model: GetTranslation( `models.classes.${str(modelName).singular().value()}`, { choice: 1 }, ), @@ -91,7 +81,9 @@ export default function Show({ }, { accessorKey: "config", - header: str(useTrans("validation.custom.config")).ucFirst().value(), + header: str(GetTranslation("validation.custom.config")) + .ucFirst() + .value(), cell: ({ value }: { value: PageConfigModel }) => auth.policies.pageConfigs.viewAny ? ( - {useTrans("back.access_model", { + {GetTranslation("back.access_model", { params: { - model: useTrans( + model: GetTranslation( "models.classes.page-config", { choice: 1 }, ), @@ -124,7 +116,7 @@ export default function Show({ }, { accessorKey: "content", - header: str(useTrans("validation.attributes.content")) + header: str(GetTranslation("validation.attributes.content")) .ucFirst() .value(), cell: ({ value }: { value: TranslationField }) => { @@ -149,21 +141,25 @@ export default function Show({ } return ( - {tableNotKnownTrans} + {GetTranslation("back.table_not_known")} ); }, }, { accessorKey: "status", - header: str(useTrans(`validation.custom.status`)).ucFirst().value(), + header: str(GetTranslation(`validation.custom.status`)) + .ucFirst() + .value(), cell: ({ value }: { value: number }) => ( ), }, { accessorKey: "isLocked", - header: str(useTrans("validation.custom.locked")).ucFirst().value(), + header: str(GetTranslation("validation.custom.locked")) + .ucFirst() + .value(), cell: ({ value }: { value: boolean }) => ( )} - {str(useTrans(value ? "back.yes" : "back.no")) + {str(GetTranslation(value ? "back.yes" : "back.no")) .ucFirst() .value()} @@ -182,7 +178,9 @@ export default function Show({ }, { accessorKey: "isRoot", - header: str(useTrans("validation.custom.root")).ucFirst().value(), + header: str(GetTranslation("validation.custom.root")) + .ucFirst() + .value(), cell: ({ value }: { value: boolean }) => ( )} - {str(useTrans(value ? "back.yes" : "back.no")) + {str(GetTranslation(value ? "back.yes" : "back.no")) .ucFirst() .value()} @@ -201,32 +199,49 @@ export default function Show({ }, { accessorKey: "created_at", - header: str(useTrans("validation.attributes.created_at")) + header: str(GetTranslation("validation.attributes.created_at")) .ucFirst() .value(), cell: ({ value }: { value: string }) => ( - {useFormatedDate(value)} + {GetFormatedDate(value)} ), }, { accessorKey: "updated_at", - header: str(useTrans("validation.attributes.updated_at")) + header: str(GetTranslation("validation.attributes.updated_at")) .ucFirst() .value(), cell: ({ value }: { value: string }) => ( - {useFormatedDate(value)} + {GetFormatedDate(value)} ), }, { accessorKey: "deleted_at", - header: str(useTrans("validation.attributes.deleted_at")) + header: str(GetTranslation("validation.attributes.deleted_at")) .ucFirst() .value(), cell: ({ value }: { value: string }) => ( - {useFormatedDate(value)} + {GetFormatedDate(value)} ), }, ]; +}; + +export default function Show({ + data, + modelName, +}: { + data: SingleResource; + modelName: string; +}) { + const { auth, appEnv } = usePage().props as unknown as { + auth: AuthProp; + appEnv: AppEnv; + }; + const { viewAny } = auth.policies.dynamicPages; + const dynamicPageModel = data.data as DynamicPageModelWithRelations; + const actionModels = [Views.EDITION, Views.DUPLICATION, Views.ARCHIVATION]; + const columns = getColumns(appEnv, auth, modelName, dynamicPageModel); const singularModelName = str(modelName).singular().value(); const transTitle = useTrans("crud.helpers.visualization_model", { params: { @@ -235,8 +250,8 @@ export default function Show({ }), }, }); - const tableNotKnownTrans = useTrans("back.table_not_known"); + const transBackToList = useTrans("back.crud_back_to_list"); const beforeNode = () => { return ( viewAny && ( @@ -244,9 +259,7 @@ export default function Show({ } - tooltipContent={ -

{useTrans("back.crud_back_to_list")}

- } + tooltipContent={

{transBackToList}

} /> ) diff --git a/resources/ts/back/Pages/News/Create.tsx b/resources/ts/back/Pages/News/Create.tsx index 575550c1..31dd6565 100644 --- a/resources/ts/back/Pages/News/Create.tsx +++ b/resources/ts/back/Pages/News/Create.tsx @@ -8,7 +8,8 @@ import Form from "@/back/Pages/News/Form"; import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; import useTrans from "@/hooks/use-translations"; import { Actions, Methods } from "@/lib/enums"; -import { AuthProp, NewsModel, SingleResource } from "@/back/types/global"; +import { AuthProp, SingleResource } from "@/back/types/global"; +import { NewsModel } from "@/back/types/others"; export default function Create({ data, diff --git a/resources/ts/back/Pages/News/Edit.tsx b/resources/ts/back/Pages/News/Edit.tsx index c861b5b3..af26d94f 100644 --- a/resources/ts/back/Pages/News/Edit.tsx +++ b/resources/ts/back/Pages/News/Edit.tsx @@ -9,7 +9,8 @@ import Form from "@/back/Pages/News/Form"; import useTrans from "@/hooks/use-translations"; import { Actions, Methods } from "@/lib/enums"; import { Views } from "@/lib/utils"; -import { AuthProp, NewsModel, SingleResource } from "@/back/types/global"; +import { AuthProp, SingleResource } from "@/back/types/global"; +import { NewsModel } from "@/back/types/others"; import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; export default function Edit({ diff --git a/resources/ts/back/Pages/News/Form.tsx b/resources/ts/back/Pages/News/Form.tsx index 3ba29a8c..3de78627 100644 --- a/resources/ts/back/Pages/News/Form.tsx +++ b/resources/ts/back/Pages/News/Form.tsx @@ -18,13 +18,9 @@ import CompleteInputFile from "@/back/Components/Inputs/CompleteInputFile"; import SimpleSwitch from "@/back/Components/Inputs/SimpleSwitch"; import SimpleVimeoId from "@/back/Components/Inputs/SimpleVimeoId"; import { cn } from "@/lib/utils"; -import { Methods } from "@/lib/enums"; -import { - AppEnv, - DynamicFieldTypes, - LaravelEnum, - NewsModel, -} from "@/back/types/global"; +import { AppEnv, LaravelEnum } from "@/back/types/global"; +import { DynamicFieldTypes } from "@/back/types/dynamic-pages"; +import { NewsModel } from "@/back/types/others"; import useTrans from "@/hooks/use-translations"; import str from "@/hooks/use-string"; import useCrudSubmit from "@/hooks/use-crud-submit"; @@ -59,9 +55,9 @@ export default function Form({ model?.categories, ); - function isFile(value: unknown): value is File { + const isFile = (value: unknown): value is File => { return value instanceof File; - } + }; /** Form section */ const { post, setData, data, reset } = useForm({ @@ -85,10 +81,10 @@ export default function Form({ const refreshFileFromDb = () => { if (model && model.picture) { - const isGif = + const newIsGifValue = typeof model.picture === "string" && model.picture.toLowerCase().endsWith(".gif"); - setIsGif(isGif); + setIsGif(newIsGifValue); setData("picture", model.picture); setDynamicKey(Date.now()); } @@ -101,7 +97,7 @@ export default function Form({ action, model, post, - data + data, }); useEffect(() => { @@ -114,6 +110,7 @@ export default function Form({ } return false; }); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return ( @@ -372,7 +369,7 @@ export default function Form({ .value()} setData={setData} onRefreshFile={refreshFileFromDb} - // @ts-ignore + // @ts-expect-error Types diff between custom reset and useForm reset. reset={reset} value={data.picture} required diff --git a/resources/ts/back/Pages/News/Index.tsx b/resources/ts/back/Pages/News/Index.tsx index 055f96b3..92379494 100644 --- a/resources/ts/back/Pages/News/Index.tsx +++ b/resources/ts/back/Pages/News/Index.tsx @@ -19,65 +19,37 @@ import DataTableFilterButton from "@/back/Components/Table/DataTableFilterButton import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; import NewsStatusBadge from "@/back/Components/News/NewsStatusBadge"; import SoftdeleteWarning from "@/back/Components/SoftdeleteWarning"; -import { Views } from "@/lib/utils"; -import { useFormatedDate } from "@/hooks/use-formated-value"; -import { - AppEnv, - AuthProp, - NewsCategoryModel, - NewsModel, - Resource, -} from "@/back/types/global"; +import RenderTranslationCell from "@/back/Components/Table/RenderTranslationCell"; +import { GetFormatedDate, GetTranslation, Views } from "@/lib/utils"; +import { AppEnv, AuthProp, Resource } from "@/back/types/global"; +import { NewsModel, NewsCategoryModel } from "@/back/types/others"; import str from "@/hooks/use-string"; import useTrans from "@/hooks/use-translations"; -import { renderTranslationCell } from "@/lib/translationCellFallback"; -export default function Index({ - newsModels, - searchFields, - modelName, -}: { - newsModels: Resource; - searchFields: string; - modelName: string; -}) { - const { appEnv, auth } = usePage().props as unknown as { - appEnv: AppEnv; - auth: AuthProp; - }; - const { viewAny, viewTrash, create } = auth.policies.news; - const { viewAny: viewAnyNewsCategories } = auth.policies.newsCategories; - const { viewAny: viewAnyNewsTags } = auth.policies.newsTags; - const isArchiveRoute = route().current(`back.${modelName}.trash`); - const actionModels = isArchiveRoute - ? [Views.RESTORATION, Views.FORCEDELETION] - : [ - Views.VISUALIZATION, - Views.EDITION, - Views.DUPLICATION, - Views.ARCHIVATION, - ]; - const columns = [ +const getColumns = (appEnv: AppEnv, viewAnyNewsCategories: boolean) => { + return [ { accessorKey: "title", header: ({ column }: { column: { id: string } }) => ( ), - cell: ({ row }: { row: Row }) => { - return renderTranslationCell(row.getValue("title")); - }, + cell: ({ row }: { row: Row }) => ( + + ), }, { accessorKey: "categories", header: () => (

{str( - useTrans("models.classes.news-category", { + GetTranslation("models.classes.news-category", { choice: 2, }), ) @@ -108,14 +80,17 @@ export default function Index({ } tooltipContent={

- {useTrans("back.access_model", { - params: { - model: useTrans( - "models.classes.news-category", - { choice: 1 }, - ), + {GetTranslation( + "back.access_model", + { + params: { + model: GetTranslation( + "models.classes.news-category", + { choice: 1 }, + ), + }, }, - })} + )}

} type="button" @@ -131,7 +106,7 @@ export default function Index({ ) : (

- {useTrans("back.table_not_known")} + {GetTranslation("back.table_not_known")}

); }, @@ -140,7 +115,7 @@ export default function Index({ accessorKey: "status", header: ({ column }: { column: { id: string } }) => ( }) => { return ( - + ); }, }, @@ -156,7 +131,9 @@ export default function Index({ accessorKey: "updated_at", header: ({ column }: { column: { id: string } }) => ( }) => ( - {useFormatedDate(row.getValue("updated_at"))} + {GetFormatedDate(row.getValue("updated_at"))} ), }, ]; +}; + +export default function Index({ + newsModels, + searchFields, + modelName, +}: { + newsModels: Resource; + searchFields: string; + modelName: string; +}) { + const { appEnv, auth } = usePage().props as unknown as { + appEnv: AppEnv; + auth: AuthProp; + }; + const { viewAny, viewTrash, create } = auth.policies.news; + const { viewAny: viewAnyNewsCategories } = auth.policies.newsCategories; + const { viewAny: viewAnyNewsTags } = auth.policies.newsTags; + const isArchiveRoute = route().current(`back.${modelName}.trash`); + const actionModels = isArchiveRoute + ? [Views.RESTORATION, Views.FORCEDELETION] + : [ + Views.VISUALIZATION, + Views.EDITION, + Views.DUPLICATION, + Views.ARCHIVATION, + ]; + const columns = getColumns(appEnv, viewAnyNewsCategories); const transModel = useTrans(`models.classes.${modelName}`, { choice: 2 }); const transArchiveTitle = useTrans("crud.helpers.archives_model", { @@ -178,6 +183,7 @@ export default function Index({ .ucFirst() .value(); + const transCrudBackList = useTrans("back.crud_back_to_list"); const beforeNode = () => { return ( isArchiveRoute && @@ -186,14 +192,29 @@ export default function Index({ } - tooltipContent={ -

{useTrans("back.crud_back_to_list")}

- } + tooltipContent={

{transCrudBackList}

} /> ) ); }; + + const transBackAccessToNewsCategory = useTrans("back.crud_access_to", { + params: { + target: useTrans("models.classes.news-category", { choice: 2 }), + }, + }); + const transBackAccessToNewsTag = useTrans("back.crud_access_to", { + params: { + target: useTrans("models.classes.news-tag", { choice: 2 }), + }, + }); + const transBackAccessToArchived = useTrans("back.crud_access_to", { + params: { + target: useTrans("back.archived_entries"), + }, + }); + const transBackAddEntry = useTrans("back.crud_add_entry"); const afterNode = () => { return ( !isArchiveRoute && ( @@ -203,16 +224,7 @@ export default function Index({ link={route(`back.news-categories.index`)} content={} tooltipContent={ -

- {useTrans("back.crud_access_to", { - params: { - target: useTrans( - "models.classes.news-category", - { choice: 2 }, - ), - }, - })} -

+

{transBackAccessToNewsCategory}

} variant="outline" /> @@ -221,18 +233,7 @@ export default function Index({ } - tooltipContent={ -

- {useTrans("back.crud_access_to", { - params: { - target: useTrans( - "models.classes.news-tag", - { choice: 2 }, - ), - }, - })} -

- } + tooltipContent={

{transBackAccessToNewsTag}

} variant="outline" /> )} @@ -240,17 +241,7 @@ export default function Index({ } - tooltipContent={ -

- {useTrans("back.crud_access_to", { - params: { - target: useTrans( - "back.archived_entries", - ), - }, - })} -

- } + tooltipContent={

{transBackAccessToArchived}

} variant="outline" /> )} @@ -258,9 +249,7 @@ export default function Index({ } - tooltipContent={ -

{useTrans("back.crud_add_entry")}

- } + tooltipContent={

{transBackAddEntry}

} /> )} diff --git a/resources/ts/back/Pages/News/Show.tsx b/resources/ts/back/Pages/News/Show.tsx index 94cccd96..435d550d 100644 --- a/resources/ts/back/Pages/News/Show.tsx +++ b/resources/ts/back/Pages/News/Show.tsx @@ -8,49 +8,30 @@ import { Badge } from "@/components/ui/badge"; import AuthenticatedLayout from "@/back/Layouts/AuthenticatedLayout"; import DetailsTable from "@/back/Components/Table/DetailsTable"; import ModelActions from "@/back/Components/ModelActions"; -import { Views } from "@/lib/utils"; -import { useFormatedDate } from "@/hooks/use-formated-value"; +import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; +import RenderTranslationCell from "@/back/Components/Table/RenderTranslationCell"; +import NewsStatusBadge from "@/back/Components/News/NewsStatusBadge"; +import { GetFormatedDate, GetTranslation, Views } from "@/lib/utils"; +import { AppEnv, AuthProp, SingleResource } from "@/back/types/global"; import { - AppEnv, - AuthProp, - NewsCategoryModel, NewsModel, + NewsCategoryModel, NewsTagModel, - SingleResource, - TranslationField, -} from "@/back/types/global"; +} from "@/back/types/others"; +import { TranslationField } from "@/back/types/i18n"; import str from "@/hooks/use-string"; import useTrans from "@/hooks/use-translations"; -import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; -import NewsStatusBadge from "@/back/Components/News/NewsStatusBadge"; import useLocale from "@/store/use-locale-store"; -import { renderTranslationCell } from "@/lib/translationCellFallback"; -export default function Show({ - data, - modelName, -}: { - data: SingleResource; - modelName: string; -}) { - const { auth, appEnv } = usePage().props as unknown as { - auth: AuthProp; - appEnv: AppEnv; - }; - const { viewAny } = auth.policies.news; - const { viewAny: viewAnyNewsCategories } = auth.policies.newsCategories; - const { viewAny: viewAnyNewsTags } = auth.policies.newsTags; - const newsModel = data.data as NewsModel; - const actionModels = [Views.EDITION, Views.DUPLICATION, Views.ARCHIVATION]; - const transTitle = useTrans("crud.helpers.visualization_model", { - params: { - model: useTrans(`models.classes.${modelName}`, { choice: 1 }), - }, - }); - const columns = [ +const getColumns = ( + appEnv: AppEnv, + viewAnyNewsCategories: boolean, + viewAnyNewsTags: boolean, +) => { + return [ { accessorKey: "seo_title", - header: str(useTrans("validation.custom.seo.title")) + header: str(GetTranslation("validation.custom.seo.title")) .ucFirst() .value(), cell: ({ value }: { value: TranslationField }) => @@ -58,13 +39,13 @@ export default function Show({ value[appEnv.locale] ) : (

- {useTrans("back.table_not_known")} + {GetTranslation("back.table_not_known")}

), }, { accessorKey: "seo_description", - header: str(useTrans("validation.custom.seo.description")) + header: str(GetTranslation("validation.custom.seo.description")) .ucFirst() .value(), cell: ({ value }: { value: TranslationField }) => @@ -72,34 +53,36 @@ export default function Show({ value[appEnv.locale] ) : (

- {useTrans("back.table_not_known")} + {GetTranslation("back.table_not_known")}

), }, { accessorKey: "title", - header: str(useTrans("validation.attributes.title")) + header: str(GetTranslation("validation.attributes.title")) .ucFirst() .value(), - cell: ({ value }: { value: TranslationField }) => { - return renderTranslationCell(value); - }, + cell: ({ value }: { value: TranslationField }) => ( + + ), }, { accessorKey: "slug", - header: str(useTrans("validation.custom.slug")).ucFirst().value(), + header: str(GetTranslation("validation.custom.slug")) + .ucFirst() + .value(), cell: ({ value }: { value: TranslationField }) => value[appEnv.locale] ? ( value[appEnv.locale] ) : (

- {useTrans("back.table_not_known")} + {GetTranslation("back.table_not_known")}

), }, { accessorKey: "content", - header: str(useTrans("validation.attributes.content")) + header: str(GetTranslation("validation.attributes.content")) .ucFirst() .value(), cell: ({ value }: { value: TranslationField }) => @@ -107,20 +90,24 @@ export default function Show({ value[appEnv.locale] ) : (

- {useTrans("back.table_not_known")} + {GetTranslation("back.table_not_known")}

), }, { accessorKey: "status", - header: str(useTrans("validation.custom.status")).ucFirst().value(), + header: str(GetTranslation("validation.custom.status")) + .ucFirst() + .value(), cell: ({ value }: { value: string }) => { - return ; + return ; }, }, { accessorKey: "categories", - header: str(useTrans("models.classes.news-category", { choice: 2 })) + header: str( + GetTranslation("models.classes.news-category", { choice: 2 }), + ) .ucFirst() .value(), cell: ({ value }: { value?: NewsCategoryModel[] }) => { @@ -143,14 +130,17 @@ export default function Show({ } tooltipContent={

- {useTrans("back.access_model", { - params: { - model: useTrans( - "models.classes.news-category", - { choice: 1 }, - ), + {GetTranslation( + "back.access_model", + { + params: { + model: GetTranslation( + "models.classes.news-category", + { choice: 1 }, + ), + }, }, - })} + )}

} type="button" @@ -166,14 +156,16 @@ export default function Show({ ) : (

- {useTrans("back.table_not_known")} + {GetTranslation("back.table_not_known")}

); }, }, { accessorKey: "tags", - header: str(useTrans("models.classes.news-tag", { choice: 2 })) + header: str( + GetTranslation("models.classes.news-tag", { choice: 2 }), + ) .ucFirst() .value(), cell: ({ value }: { value?: NewsTagModel[] }) => { @@ -196,14 +188,17 @@ export default function Show({ } tooltipContent={

- {useTrans("back.access_model", { - params: { - model: useTrans( - "models.classes.news-category", - { choice: 1 }, - ), + {GetTranslation( + "back.access_model", + { + params: { + model: GetTranslation( + "models.classes.news-category", + { choice: 1 }, + ), + }, }, - })} + )}

} type="button" @@ -219,34 +214,34 @@ export default function Show({ ) : (

- {useTrans("back.table_not_known")} + {GetTranslation("back.table_not_known")}

); }, }, { accessorKey: "created_at", - header: str(useTrans("validation.attributes.created_at")) + header: str(GetTranslation("validation.attributes.created_at")) .ucFirst() .value(), cell: ({ value }: { value: string }) => { return ( - {useFormatedDate(value)} + {GetFormatedDate(value)} ); }, }, { accessorKey: "picture", - header: str(useTrans("validation.attributes.image")) + header: str(GetTranslation("validation.attributes.image")) .ucFirst() .value(), cell: ({ value }: { value: string }) => ( - + ), }, { accessorKey: "picture_alt", - header: str(useTrans("validation.custom.image_alt")) + header: str(GetTranslation("validation.custom.image_alt")) .ucFirst() .value(), cell: ({ value }: { value: TranslationField }) => @@ -254,13 +249,13 @@ export default function Show({ value[appEnv.locale] ) : (

- {useTrans("back.table_not_known")} + {GetTranslation("back.table_not_known")}

), }, { accessorKey: "picture_title", - header: str(useTrans("validation.custom.image_title")) + header: str(GetTranslation("validation.custom.image_title")) .ucFirst() .value(), cell: ({ value }: { value: TranslationField }) => @@ -268,50 +263,74 @@ export default function Show({ value[appEnv.locale] ) : (

- {useTrans("back.table_not_known")} + {GetTranslation("back.table_not_known")}

), }, { accessorKey: "published_at", - header: str(useTrans("validation.custom.published_at")) + header: str(GetTranslation("validation.custom.published_at")) .ucFirst() .value(), cell: ({ value }: { value: string }) => ( - {useFormatedDate(value, useLocale.use.timezone(), true)} + {GetFormatedDate(value, useLocale.use.timezone(), true)} ), }, { accessorKey: "unpublished_at", - header: str(useTrans("validation.custom.unpublished_at")) + header: str(GetTranslation("validation.custom.unpublished_at")) .ucFirst() .value(), cell: ({ value }: { value: string }) => ( - {useFormatedDate(value)} + {GetFormatedDate(value)} ), }, { accessorKey: "updated_at", - header: str(useTrans("validation.attributes.updated_at")) + header: str(GetTranslation("validation.attributes.updated_at")) .ucFirst() .value(), cell: ({ value }: { value: string }) => ( - {useFormatedDate(value)} + {GetFormatedDate(value)} ), }, { accessorKey: "deleted_at", - header: str(useTrans("validation.attributes.deleted_at")) + header: str(GetTranslation("validation.attributes.deleted_at")) .ucFirst() .value(), cell: ({ value }: { value: string }) => ( - {useFormatedDate(value)} + {GetFormatedDate(value)} ), }, ]; +}; +export default function Show({ + data, + modelName, +}: { + data: SingleResource; + modelName: string; +}) { + const { auth, appEnv } = usePage().props as unknown as { + auth: AuthProp; + appEnv: AppEnv; + }; + const { viewAny } = auth.policies.news; + const { viewAny: viewAnyNewsCategories } = auth.policies.newsCategories; + const { viewAny: viewAnyNewsTags } = auth.policies.newsTags; + const newsModel = data.data as NewsModel; + const actionModels = [Views.EDITION, Views.DUPLICATION, Views.ARCHIVATION]; + const transTitle = useTrans("crud.helpers.visualization_model", { + params: { + model: useTrans(`models.classes.${modelName}`, { choice: 1 }), + }, + }); + const columns = getColumns(appEnv, viewAnyNewsCategories, viewAnyNewsTags); + const transCrudBackList = useTrans("back.crud_back_to_list"); const beforeNode = () => { return ( viewAny && ( @@ -319,9 +338,7 @@ export default function Show({ } - tooltipContent={ -

{useTrans("back.crud_back_to_list")}

- } + tooltipContent={

{transCrudBackList}

} /> ) diff --git a/resources/ts/back/Pages/NewsCategory/Create.tsx b/resources/ts/back/Pages/NewsCategory/Create.tsx index bec037d6..2bc61769 100644 --- a/resources/ts/back/Pages/NewsCategory/Create.tsx +++ b/resources/ts/back/Pages/NewsCategory/Create.tsx @@ -7,11 +7,8 @@ import AuthenticatedLayout from "@/back/Layouts/AuthenticatedLayout"; import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; import useTrans from "@/hooks/use-translations"; import { Actions, Methods } from "@/lib/enums"; -import { - AuthProp, - NewsCategoryModel, - SingleResource, -} from "@/back/types/global"; +import { AuthProp, SingleResource } from "@/back/types/global"; +import { NewsCategoryModel } from "@/back/types/others"; import Form from "@/back/Pages/NewsCategory/Form"; import str from "@/hooks/use-string"; diff --git a/resources/ts/back/Pages/NewsCategory/Edit.tsx b/resources/ts/back/Pages/NewsCategory/Edit.tsx index 096ef803..6c5ef94d 100644 --- a/resources/ts/back/Pages/NewsCategory/Edit.tsx +++ b/resources/ts/back/Pages/NewsCategory/Edit.tsx @@ -10,11 +10,8 @@ import useTrans from "@/hooks/use-translations"; import { Actions, Methods } from "@/lib/enums"; import Form from "@/back/Pages/NewsCategory/Form"; import { Views } from "@/lib/utils"; -import { - AuthProp, - NewsCategoryModel, - SingleResource, -} from "@/back/types/global"; +import { AuthProp, SingleResource } from "@/back/types/global"; +import { NewsCategoryModel } from "@/back/types/others"; import str from "@/hooks/use-string"; export default function Edit({ diff --git a/resources/ts/back/Pages/NewsCategory/Form.tsx b/resources/ts/back/Pages/NewsCategory/Form.tsx index 7722fcd7..d30a7e34 100644 --- a/resources/ts/back/Pages/NewsCategory/Form.tsx +++ b/resources/ts/back/Pages/NewsCategory/Form.tsx @@ -5,7 +5,8 @@ import { BookTextIcon } from "lucide-react"; import FieldsetWrapper from "@/back/Components/FieldsetWrapper"; import InputError from "@/back/Components/InputError"; import SimpleString from "@/back/Components/Inputs/SimpleString"; -import { AppEnv, NewsCategoryModel } from "@/back/types/global"; +import { AppEnv } from "@/back/types/global"; +import { NewsCategoryModel } from "@/back/types/others"; import useTrans from "@/hooks/use-translations"; import str from "@/hooks/use-string"; import useCrudSubmit from "@/hooks/use-crud-submit"; diff --git a/resources/ts/back/Pages/NewsCategory/Index.tsx b/resources/ts/back/Pages/NewsCategory/Index.tsx index 5f49a576..8b743784 100644 --- a/resources/ts/back/Pages/NewsCategory/Index.tsx +++ b/resources/ts/back/Pages/NewsCategory/Index.tsx @@ -11,13 +11,76 @@ import DataTable from "@/back/Components/Table/DataTable"; import DataTableSearch from "@/back/Components/Table/DataTableSearch"; import DataTableFilterButton from "@/back/Components/Table/DataTableFilterButton"; import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; +import RenderTranslationCell from "@/back/Components/Table/RenderTranslationCell"; import SoftdeleteWarning from "@/back/Components/SoftdeleteWarning"; -import { Views } from "@/lib/utils"; -import { useFormatedDate } from "@/hooks/use-formated-value"; -import { AuthProp, NewsCategoryModel, Resource } from "@/back/types/global"; +import { GetFormatedDate, GetTranslation, Views } from "@/lib/utils"; +import { AuthProp, Resource } from "@/back/types/global"; +import { NewsCategoryModel } from "@/back/types/others"; import str from "@/hooks/use-string"; import useTrans from "@/hooks/use-translations"; -import { renderTranslationCell } from "@/lib/translationCellFallback"; + +const getColumns = () => { + return [ + { + accessorKey: "name", + header: ({ column }: { column: { id: string } }) => ( + + ), + cell: ({ row }: { row: Row }) => ( + + ), + }, + { + accessorKey: "news", + header: () => ( +

+ {str( + GetTranslation("back.associated_model", { + params: { + model: GetTranslation("models.classes.news", { + choice: 2, + }), + }, + choice: 2, + }), + ) + .ucFirst() + .value()} +

+ ), + cell: ({ row }: { row: Row }) => ( +
+

{row.original.news_count ?? 0}

+
+ ), + }, + { + accessorKey: "updated_at", + header: ({ column }: { column: { id: string } }) => ( + + ), + cell: ({ row }: { row: Row }) => ( + + {GetFormatedDate(row.getValue("updated_at"))} + + ), + }, + ]; +}; export default function Index({ newsCategoryModels, @@ -41,62 +104,7 @@ export default function Index({ Views.DUPLICATION, Views.ARCHIVATION, ]; - const columns = [ - { - accessorKey: "name", - header: ({ column }: { column: { id: string } }) => ( - - ), - cell: ({ row }: { row: Row }) => { - return renderTranslationCell(row.getValue("name")); - }, - }, - { - accessorKey: "news", - header: () => ( -

- {str( - useTrans("back.associated_model", { - params: { - model: useTrans("models.classes.news", { - choice: 2, - }), - }, - choice: 2, - }), - ) - .ucFirst() - .value()} -

- ), - cell: ({ row }: { row: Row }) => ( -
-

{row.original.news_count ?? 0}

-
- ), - }, - { - accessorKey: "updated_at", - header: ({ column }: { column: { id: string } }) => ( - - ), - cell: ({ row }: { row: Row }) => ( - - {useFormatedDate(row.getValue("updated_at"))} - - ), - }, - ]; + const columns = getColumns(); const transModel = useTrans( `models.classes.${str(modelName).singular().value()}`, { choice: 2 }, @@ -108,6 +116,7 @@ export default function Index({ .ucFirst() .value(); + const transCrudBackList = useTrans("back.crud_back_to_list"); const beforeNode = () => { return ( isArchiveRoute && @@ -116,14 +125,19 @@ export default function Index({ } - tooltipContent={ -

{useTrans("back.crud_back_to_list")}

- } + tooltipContent={

{transCrudBackList}

} /> ) ); }; + + const transCrudAccessTo = useTrans("back.crud_access_to", { + params: { + target: useTrans("back.archived_entries"), + }, + }); + const transCrudAddEntry = useTrans("back.crud_add_entry"); const afterNode = () => { return ( !isArchiveRoute && ( @@ -132,17 +146,7 @@ export default function Index({ } - tooltipContent={ -

- {useTrans("back.crud_access_to", { - params: { - target: useTrans( - "back.archived_entries", - ), - }, - })} -

- } + tooltipContent={

{transCrudAccessTo}

} variant="outline" /> )} @@ -150,9 +154,7 @@ export default function Index({ } - tooltipContent={ -

{useTrans("back.crud_add_entry")}

- } + tooltipContent={

{transCrudAddEntry}

} /> )} diff --git a/resources/ts/back/Pages/NewsCategory/Show.tsx b/resources/ts/back/Pages/NewsCategory/Show.tsx index 16ce6c8f..6755d441 100644 --- a/resources/ts/back/Pages/NewsCategory/Show.tsx +++ b/resources/ts/back/Pages/NewsCategory/Show.tsx @@ -8,21 +8,68 @@ import { Badge } from "@/components/ui/badge"; import AuthenticatedLayout from "@/back/Layouts/AuthenticatedLayout"; import DetailsTable from "@/back/Components/Table/DetailsTable"; import ModelActions from "@/back/Components/ModelActions"; -import { Views } from "@/lib/utils"; -import { useFormatedDate } from "@/hooks/use-formated-value"; +import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; +import AssociatedNewsList from "@/back/Components/News/AssociatedNewsList"; +import RenderTranslationCell from "@/back/Components/Table/RenderTranslationCell"; +import { GetFormatedDate, GetTranslation, Views } from "@/lib/utils"; import { AppEnv, AuthProp, - NewsCategoryModel, Resource, SingleResource, - TranslationField, } from "@/back/types/global"; +import { NewsCategoryModel } from "@/back/types/others"; +import { TranslationField } from "@/back/types/i18n"; import str from "@/hooks/use-string"; import useTrans from "@/hooks/use-translations"; -import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; -import AssociatedNewsList from "@/back/Components/News/AssociatedNewsList"; -import { renderTranslationCell } from "@/lib/translationCellFallback"; + +const getColumns = (appEnv: AppEnv) => { + return [ + { + accessorKey: "name", + header: str(GetTranslation("validation.attributes.name")) + .ucFirst() + .value(), + cell: ({ value }: { value: TranslationField }) => ( + + ), + }, + { + accessorKey: "slug", + header: str(GetTranslation("validation.custom.slug")) + .ucFirst() + .value(), + cell: ({ value }: { value: TranslationField }) => + value[appEnv.locale] ? ( + value[appEnv.locale] + ) : ( +

+ {GetTranslation("back.table_not_known")} +

+ ), + }, + { + accessorKey: "created_at", + header: str(GetTranslation("validation.attributes.created_at")) + .ucFirst() + .value(), + cell: ({ value }: { value: string }) => { + return ( + {GetFormatedDate(value)} + ); + }, + }, + { + accessorKey: "updated_at", + header: str(GetTranslation("validation.attributes.updated_at")) + .ucFirst() + .value(), + cell: ({ value }: { value: string }) => ( + {GetFormatedDate(value)} + ), + }, + ]; +}; export default function Show({ data, @@ -48,50 +95,9 @@ export default function Show({ }), }, }); - const columns = [ - { - accessorKey: "name", - header: str(useTrans("validation.attributes.name")) - .ucFirst() - .value(), - cell: ({ value }: { value: TranslationField }) => { - return renderTranslationCell(value); - }, - }, - { - accessorKey: "slug", - header: str(useTrans("validation.custom.slug")).ucFirst().value(), - cell: ({ value }: { value: TranslationField }) => - value[appEnv.locale] ? ( - value[appEnv.locale] - ) : ( -

- {useTrans("back.table_not_known")} -

- ), - }, - { - accessorKey: "created_at", - header: str(useTrans("validation.attributes.created_at")) - .ucFirst() - .value(), - cell: ({ value }: { value: string }) => { - return ( - {useFormatedDate(value)} - ); - }, - }, - { - accessorKey: "updated_at", - header: str(useTrans("validation.attributes.updated_at")) - .ucFirst() - .value(), - cell: ({ value }: { value: string }) => ( - {useFormatedDate(value)} - ), - }, - ]; + const columns = getColumns(appEnv); + const transCrudBackList = useTrans("back.crud_back_to_list"); const beforeNode = () => { return ( viewAny && ( @@ -99,9 +105,7 @@ export default function Show({ } - tooltipContent={ -

{useTrans("back.crud_back_to_list")}

- } + tooltipContent={

{transCrudBackList}

} /> ) diff --git a/resources/ts/back/Pages/NewsTag/Create.tsx b/resources/ts/back/Pages/NewsTag/Create.tsx index 0a162923..b4b520e7 100644 --- a/resources/ts/back/Pages/NewsTag/Create.tsx +++ b/resources/ts/back/Pages/NewsTag/Create.tsx @@ -7,7 +7,8 @@ import AuthenticatedLayout from "@/back/Layouts/AuthenticatedLayout"; import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; import useTrans from "@/hooks/use-translations"; import { Actions, Methods } from "@/lib/enums"; -import { AuthProp, NewsTagModel, SingleResource } from "@/back/types/global"; +import { AuthProp, SingleResource } from "@/back/types/global"; +import { NewsTagModel } from "@/back/types/others"; import Form from "@/back/Pages/NewsTag/Form"; import str from "@/hooks/use-string"; diff --git a/resources/ts/back/Pages/NewsTag/Edit.tsx b/resources/ts/back/Pages/NewsTag/Edit.tsx index 84cca0ea..748a2c55 100644 --- a/resources/ts/back/Pages/NewsTag/Edit.tsx +++ b/resources/ts/back/Pages/NewsTag/Edit.tsx @@ -9,7 +9,8 @@ import useTrans from "@/hooks/use-translations"; import { Actions, Methods } from "@/lib/enums"; import Form from "@/back/Pages/NewsTag/Form"; import { Views } from "@/lib/utils"; -import { AuthProp, NewsTagModel, SingleResource } from "@/back/types/global"; +import { AuthProp, SingleResource } from "@/back/types/global"; +import { NewsTagModel } from "@/back/types/others"; import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; import str from "@/hooks/use-string"; diff --git a/resources/ts/back/Pages/NewsTag/Form.tsx b/resources/ts/back/Pages/NewsTag/Form.tsx index 0cf6c7b6..15bfb372 100644 --- a/resources/ts/back/Pages/NewsTag/Form.tsx +++ b/resources/ts/back/Pages/NewsTag/Form.tsx @@ -8,7 +8,8 @@ import SimpleString from "@/back/Components/Inputs/SimpleString"; import SubmitButton from "@/back/Components/SubmitButton"; import SimpleColorPicker from "@/back/Components/Inputs/SimpleColorPicker"; import Slugifycator from "@/back/Components/Inputs/Slugifycator"; -import { AppEnv, NewsTagModel } from "@/back/types/global"; +import { AppEnv } from "@/back/types/global"; +import { NewsTagModel } from "@/back/types/others"; import useTrans from "@/hooks/use-translations"; import str from "@/hooks/use-string"; import useCrudSubmit from "@/hooks/use-crud-submit"; diff --git a/resources/ts/back/Pages/NewsTag/Index.tsx b/resources/ts/back/Pages/NewsTag/Index.tsx index 3eb884e3..f841f20d 100644 --- a/resources/ts/back/Pages/NewsTag/Index.tsx +++ b/resources/ts/back/Pages/NewsTag/Index.tsx @@ -16,56 +16,37 @@ import DataTable from "@/back/Components/Table/DataTable"; import DataTableSearch from "@/back/Components/Table/DataTableSearch"; import DataTableFilterButton from "@/back/Components/Table/DataTableFilterButton"; import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; +import RenderTranslationCell from "@/back/Components/Table/RenderTranslationCell"; import SoftdeleteWarning from "@/back/Components/SoftdeleteWarning"; -import { Views } from "@/lib/utils"; -import { useFormatedDate } from "@/hooks/use-formated-value"; -import { AuthProp, NewsTagModel, Resource } from "@/back/types/global"; +import { GetFormatedDate, GetTranslation, Views } from "@/lib/utils"; +import { AuthProp, Resource } from "@/back/types/global"; +import { NewsTagModel } from "@/back/types/others"; import str from "@/hooks/use-string"; import useTrans from "@/hooks/use-translations"; -import { renderTranslationCell } from "@/lib/translationCellFallback"; -export default function Index({ - newsTagModels, - searchFields, - modelName, -}: { - newsTagModels: Resource; - searchFields: string; - modelName: string; -}) { - const { auth } = usePage().props as unknown as { - auth: AuthProp; - }; - const { viewAny, viewTrash, create } = auth.policies.newsTags; - const isArchiveRoute = route().current(`back.${modelName}.trash`); - const actionModels = isArchiveRoute - ? [Views.RESTORATION, Views.FORCEDELETION] - : [ - Views.VISUALIZATION, - Views.EDITION, - Views.DUPLICATION, - Views.ARCHIVATION, - ]; - const columns = [ +const getColumns = () => { + return [ { accessorKey: "name", header: ({ column }: { column: { id: string } }) => ( ), - cell: ({ row }: { row: Row }) => { - return renderTranslationCell(row.getValue("name")); - }, + cell: ({ row }: { row: Row }) => ( + + ), }, { accessorKey: "color", header: ({ column }: { column: { id: string } }) => ( (

{str( - useTrans("back.associated_model", { + GetTranslation("back.associated_model", { params: { - model: useTrans("models.classes.news", { + model: GetTranslation("models.classes.news", { choice: 2, }), }, @@ -115,7 +96,9 @@ export default function Index({ accessorKey: "updated_at", header: ({ column }: { column: { id: string } }) => ( }) => ( - {useFormatedDate(row.getValue("updated_at"))} + {GetFormatedDate(row.getValue("updated_at"))} ), }, ]; +}; + +export default function Index({ + newsTagModels, + searchFields, + modelName, +}: { + newsTagModels: Resource; + searchFields: string; + modelName: string; +}) { + const { auth } = usePage().props as unknown as { + auth: AuthProp; + }; + const { viewAny, viewTrash, create } = auth.policies.newsTags; + const isArchiveRoute = route().current(`back.${modelName}.trash`); + const actionModels = isArchiveRoute + ? [Views.RESTORATION, Views.FORCEDELETION] + : [ + Views.VISUALIZATION, + Views.EDITION, + Views.DUPLICATION, + Views.ARCHIVATION, + ]; + const columns = getColumns(); const transModel = useTrans( `models.classes.${str(modelName).singular().value()}`, { choice: 2 }, @@ -139,6 +147,7 @@ export default function Index({ .ucFirst() .value(); + const transCrudBackList = useTrans("back.crud_back_to_list"); const beforeNode = () => { return ( isArchiveRoute && @@ -147,14 +156,19 @@ export default function Index({ } - tooltipContent={ -

{useTrans("back.crud_back_to_list")}

- } + tooltipContent={

{transCrudBackList}

} /> ) ); }; + + const transCrudAccessTo = useTrans("back.crud_access_to", { + params: { + target: useTrans("back.archived_entries"), + }, + }); + const transCrudAddEntry = useTrans("back.crud_add_entry"); const afterNode = () => { return ( !isArchiveRoute && ( @@ -163,17 +177,7 @@ export default function Index({ } - tooltipContent={ -

- {useTrans("back.crud_access_to", { - params: { - target: useTrans( - "back.archived_entries", - ), - }, - })} -

- } + tooltipContent={

{transCrudAccessTo}

} variant="outline" /> )} @@ -181,9 +185,7 @@ export default function Index({ } - tooltipContent={ -

{useTrans("back.crud_add_entry")}

- } + tooltipContent={

{transCrudAddEntry}

} /> )} diff --git a/resources/ts/back/Pages/NewsTag/Show.tsx b/resources/ts/back/Pages/NewsTag/Show.tsx index 38476310..38c3e2a9 100644 --- a/resources/ts/back/Pages/NewsTag/Show.tsx +++ b/resources/ts/back/Pages/NewsTag/Show.tsx @@ -4,30 +4,98 @@ import { route } from "ziggy-js"; import { ArrowLeft } from "lucide-react"; // * Components shadcn import { Badge } from "@/components/ui/badge"; -// * Other -import AuthenticatedLayout from "@/back/Layouts/AuthenticatedLayout"; -import DetailsTable from "@/back/Components/Table/DetailsTable"; -import ModelActions from "@/back/Components/ModelActions"; -import { Views } from "@/lib/utils"; -import { useFormatedDate } from "@/hooks/use-formated-value"; -import { - AppEnv, - AuthProp, - NewsTagModel, - Resource, - SingleResource, - TranslationField, -} from "@/back/types/global"; -import str from "@/hooks/use-string"; -import useTrans from "@/hooks/use-translations"; -import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; import { Tooltip, TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip"; +// * Other +import AuthenticatedLayout from "@/back/Layouts/AuthenticatedLayout"; +import DetailsTable from "@/back/Components/Table/DetailsTable"; +import ModelActions from "@/back/Components/ModelActions"; +import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; import AssociatedNewsList from "@/back/Components/News/AssociatedNewsList"; -import { renderTranslationCell } from "@/lib/translationCellFallback"; +import RenderTranslationCell from "@/back/Components/Table/RenderTranslationCell"; +import { GetFormatedDate, GetTranslation, Views } from "@/lib/utils"; +import { + AppEnv, + AuthProp, + Resource, + SingleResource, +} from "@/back/types/global"; +import { NewsTagModel } from "@/back/types/others"; +import { TranslationField } from "@/back/types/i18n"; +import str from "@/hooks/use-string"; +import useTrans from "@/hooks/use-translations"; + +const getColumns = (appEnv: AppEnv) => { + return [ + { + accessorKey: "name", + header: str(GetTranslation("validation.attributes.name")) + .ucFirst() + .value(), + cell: ({ value }: { value: TranslationField }) => ( + + ), + }, + { + accessorKey: "slug", + header: str(GetTranslation("validation.custom.slug")) + .ucFirst() + .value(), + cell: ({ value }: { value: TranslationField }) => + value[appEnv.locale] ? ( + value[appEnv.locale] + ) : ( +

+ {GetTranslation("back.table_not_known")} +

+ ), + }, + { + accessorKey: "color", + header: str(GetTranslation("validation.custom.color")) + .ucFirst() + .value(), + cell: ({ value }: { value: string }) => { + return ( + + +
+ + {value} + + ); + }, + }, + { + accessorKey: "created_at", + header: str(GetTranslation("validation.attributes.created_at")) + .ucFirst() + .value(), + cell: ({ value }: { value: string }) => { + return ( + {GetFormatedDate(value)} + ); + }, + }, + { + accessorKey: "updated_at", + header: str(GetTranslation("validation.attributes.updated_at")) + .ucFirst() + .value(), + cell: ({ value }: { value: string }) => ( + {GetFormatedDate(value)} + ), + }, + ]; +}; export default function Show({ data, @@ -53,69 +121,9 @@ export default function Show({ }), }, }); - const columns = [ - { - accessorKey: "name", - header: str(useTrans("validation.attributes.name")) - .ucFirst() - .value(), - cell: ({ value }: { value: TranslationField }) => { - return renderTranslationCell(value); - }, - }, - { - accessorKey: "slug", - header: str(useTrans("validation.custom.slug")).ucFirst().value(), - cell: ({ value }: { value: TranslationField }) => - value[appEnv.locale] ? ( - value[appEnv.locale] - ) : ( -

- {useTrans("back.table_not_known")} -

- ), - }, - { - accessorKey: "color", - header: str(useTrans("validation.custom.color")).ucFirst().value(), - cell: ({ value }: { value: string }) => { - return ( - - -
- - {value} - - ); - }, - }, - { - accessorKey: "created_at", - header: str(useTrans("validation.attributes.created_at")) - .ucFirst() - .value(), - cell: ({ value }: { value: string }) => { - return ( - {useFormatedDate(value)} - ); - }, - }, - { - accessorKey: "updated_at", - header: str(useTrans("validation.attributes.updated_at")) - .ucFirst() - .value(), - cell: ({ value }: { value: string }) => ( - {useFormatedDate(value)} - ), - }, - ]; + const columns = getColumns(appEnv); + const transCrudBackList = useTrans("back.crud_back_to_list"); const beforeNode = () => { return ( viewAny && ( @@ -123,9 +131,7 @@ export default function Show({ } - tooltipContent={ -

{useTrans("back.crud_back_to_list")}

- } + tooltipContent={

{transCrudBackList}

} />
) diff --git a/resources/ts/back/Pages/PageConfigs/Create.tsx b/resources/ts/back/Pages/PageConfigs/Create.tsx index 270090ee..ee77a78a 100644 --- a/resources/ts/back/Pages/PageConfigs/Create.tsx +++ b/resources/ts/back/Pages/PageConfigs/Create.tsx @@ -8,7 +8,8 @@ import Form from "@/back/Pages/PageConfigs/Form"; import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; import useTrans from "@/hooks/use-translations"; import { Actions, Methods } from "@/lib/enums"; -import { AuthProp, PageConfigModel, SingleResource } from "@/back/types/global"; +import { AuthProp, SingleResource } from "@/back/types/global"; +import { PageConfigModel } from "@/back/types/dynamic-pages"; import str from "@/hooks/use-string"; export default function Create({ diff --git a/resources/ts/back/Pages/PageConfigs/Edit.tsx b/resources/ts/back/Pages/PageConfigs/Edit.tsx index f2fa9e7f..5e558e3f 100644 --- a/resources/ts/back/Pages/PageConfigs/Edit.tsx +++ b/resources/ts/back/Pages/PageConfigs/Edit.tsx @@ -10,7 +10,8 @@ import Form from "@/back/Pages/PageConfigs/Form"; import useTrans from "@/hooks/use-translations"; import { Actions, Methods } from "@/lib/enums"; import { Views } from "@/lib/utils"; -import { AuthProp, PageConfigModel, SingleResource } from "@/back/types/global"; +import { AuthProp, SingleResource } from "@/back/types/global"; +import { PageConfigModel } from "@/back/types/dynamic-pages"; import str from "@/hooks/use-string"; export default function Edit({ diff --git a/resources/ts/back/Pages/PageConfigs/Form.tsx b/resources/ts/back/Pages/PageConfigs/Form.tsx index 8bc5efc0..293a58a5 100644 --- a/resources/ts/back/Pages/PageConfigs/Form.tsx +++ b/resources/ts/back/Pages/PageConfigs/Form.tsx @@ -31,13 +31,11 @@ import FieldsetWrapper from "@/back/Components/FieldsetWrapper"; import useTrans from "@/hooks/use-translations"; import str from "@/hooks/use-string"; import InputError from "@/back/Components/InputError"; +import { AppEnv, AuthProp, LaravelEnum } from "@/back/types/global"; import { - AppEnv, - AuthProp, + PageConfigModelWithRelations, ConfigField, - LaravelEnum, - PageConfigModel, -} from "@/back/types/global"; +} from "@/back/types/dynamic-pages"; import SubmitButton from "@/back/Components/SubmitButton"; import useCrudSubmit from "@/hooks/use-crud-submit"; import { cn } from "@/lib/utils"; @@ -49,7 +47,7 @@ export default function Form({ action, method, }: { - model?: PageConfigModel; + model?: PageConfigModelWithRelations; modelName: string; modelRoute: string; action: string; @@ -84,7 +82,13 @@ export default function Form({ }); const transDialogCancel = useTrans("back.no_cancel"); const transDialogSave = useTrans("back.yes_update"); - + const transJsonExportTooltip = useTrans("back.json_editor.export_tooltip"); + const transModelName = useTrans(`models.classes.${modelName}`, { + choice: 1, + }); + const transSubmitBtn = useTrans(`models.classes.${modelName}`, { + choice: 1, + }); /** Form section */ const { post, setData, data } = useForm({ label: model?.label ? model?.label[appEnv.locale] : "", @@ -132,6 +136,7 @@ export default function Form({ if (isEmpty && selectedConfig !== null) { setSelectedConfig(null); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [data.config]); useEffect(() => { @@ -142,6 +147,7 @@ export default function Form({ .value(); setData("config", model?.config[lowerSelectedLocale] ?? []); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedConfig]); return ( @@ -233,20 +239,22 @@ export default function Form({ className="w-[var(--radix-select-trigger-width)]" > {otherConfigs.map(([configLocale, config]) => { - configLocale = str(configLocale) + const strConfigLocale = str(configLocale) .upperCase() .value(); + const localeFields = Object.values(config) .map((field) => field.label) .join(", "); + return ( - {configLocale}:{" "} + {strConfigLocale}:{" "} {localeFields} ); @@ -260,9 +268,7 @@ export default function Form({ filename={`${str(data.label ?? "configuration") .slug() .value()}.json`} - tooltipContent={useTrans( - "back.json_editor.export_tooltip", - )} + tooltipContent={transJsonExportTooltip} disabled={!data.config.length} /> )} @@ -283,12 +289,7 @@ export default function Form({
@@ -301,19 +302,17 @@ export default function Form({

{transDialogParagraphList}

    - {model?.pages.map( - (page, pageIndex) => ( -
  • - {page.title[ - appEnv.locale - ] ?? - page.title[ - appEnv - .fallbackLocale - ]} -
  • - ), - )} + {model?.pages.map((page) => ( +
  • + {page.title[ + appEnv.locale + ] ?? + page.title[ + appEnv + .fallbackLocale + ]} +
  • + ))}
@@ -334,11 +333,7 @@ export default function Form({ ) : ( - + )} diff --git a/resources/ts/back/Pages/PageConfigs/Index.tsx b/resources/ts/back/Pages/PageConfigs/Index.tsx index 1f475745..86d4a145 100644 --- a/resources/ts/back/Pages/PageConfigs/Index.tsx +++ b/resources/ts/back/Pages/PageConfigs/Index.tsx @@ -12,18 +12,68 @@ import DataTableSearch from "@/back/Components/Table/DataTableSearch"; import DataTableFilterButton from "@/back/Components/Table/DataTableFilterButton"; import SoftdeleteWarning from "@/back/Components/SoftdeleteWarning"; import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; -import { Views } from "@/lib/utils"; -import { useFormatedDate } from "@/hooks/use-formated-value"; -import { - AppEnv, - AuthProp, - PageConfigModel, - Resource, - TranslationField, -} from "@/back/types/global"; +import RenderTranslationCell from "@/back/Components/Table/RenderTranslationCell"; +import { GetFormatedDate, GetTranslation, Views } from "@/lib/utils"; +import { AuthProp, Resource } from "@/back/types/global"; +import { PageConfigModel } from "@/back/types/dynamic-pages"; import str from "@/hooks/use-string"; import useTrans from "@/hooks/use-translations"; -import { renderTranslationCell } from "@/lib/translationCellFallback"; + +const getColumns = () => { + return [ + { + accessorKey: "label", + header: ({ column }: { column: { id: string } }) => ( + + ), + cell: ({ row }: { row: Row }) => ( + + ), + }, + { + accessorKey: "view", + header: ({ column }: { column: { id: string } }) => ( + + ), + cell: ({ row }: { row: Row }) => + row.getValue("view") ? ( + {row.getValue("view")} + ) : ( +

+ {GetTranslation("back.table_not_known")} +

+ ), + }, + { + accessorKey: "updated_at", + header: ({ column }: { column: { id: string } }) => ( + + ), + cell: ({ row }: { row: Row }) => ( + + {GetFormatedDate(row.getValue("updated_at"))} + + ), + }, + ]; +}; export default function Index({ pageConfigModels, @@ -34,9 +84,8 @@ export default function Index({ searchFields: string; modelName: string; }) { - const { auth, appEnv } = usePage().props as unknown as { + const { auth } = usePage().props as unknown as { auth: AuthProp; - appEnv: AppEnv; }; const { viewAny, viewTrash, create } = auth.policies.pageConfigs; const isArchiveRoute = route().current(`back.${modelName}.trash`); @@ -48,59 +97,7 @@ export default function Index({ Views.DUPLICATION, Views.ARCHIVATION, ]; - const columns = [ - { - accessorKey: "label", - header: ({ column }: { column: { id: string } }) => ( - - ), - cell: ({ row }: { row: Row }) => { - return renderTranslationCell( - row.getValue("label") as TranslationField, - ); - }, - }, - { - accessorKey: "view", - header: ({ column }: { column: { id: string } }) => ( - - ), - cell: ({ row }: { row: Row }) => - row.getValue("view") ? ( - {row.getValue("view")} - ) : ( -

- {useTrans("back.table_not_known")} -

- ), - }, - { - accessorKey: "updated_at", - header: ({ column }: { column: { id: string } }) => ( - - ), - cell: ({ row }: { row: Row }) => ( - - {useFormatedDate(row.getValue("updated_at"))} - - ), - }, - ]; + const columns = getColumns(); const transModel = useTrans( `models.classes.${str(modelName).singular().value()}`, @@ -113,6 +110,7 @@ export default function Index({ .ucFirst() .value(); + const transCrudBackList = useTrans("back.crud_back_to_list"); const beforeNode = () => { return ( isArchiveRoute && @@ -121,14 +119,18 @@ export default function Index({ } - tooltipContent={ -

{useTrans("back.crud_back_to_list")}

- } + tooltipContent={

{transCrudBackList}

} /> ) ); }; + const transCrudAddEntry = useTrans("back.crud_add_entry"); + const transAccessTo = useTrans("back.crud_access_to", { + params: { + target: useTrans("back.archived_entries"), + }, + }); const afterNode = () => { return ( !isArchiveRoute && ( @@ -137,17 +139,7 @@ export default function Index({ } - tooltipContent={ -

- {useTrans("back.crud_access_to", { - params: { - target: useTrans( - "back.archived_entries", - ), - }, - })} -

- } + tooltipContent={

{transAccessTo}

} variant="outline" /> )} @@ -155,9 +147,7 @@ export default function Index({ } - tooltipContent={ -

{useTrans("back.crud_add_entry")}

- } + tooltipContent={

{transCrudAddEntry}

} /> )} diff --git a/resources/ts/back/Pages/PageConfigs/Show.tsx b/resources/ts/back/Pages/PageConfigs/Show.tsx index 8643966f..b9804890 100644 --- a/resources/ts/back/Pages/PageConfigs/Show.tsx +++ b/resources/ts/back/Pages/PageConfigs/Show.tsx @@ -9,20 +9,76 @@ import AuthenticatedLayout from "@/back/Layouts/AuthenticatedLayout"; import DetailsTable from "@/back/Components/Table/DetailsTable"; import ModelActions from "@/back/Components/ModelActions"; import ConfigDetails from "@/back/Components/PageConfigs/ConfigDetails"; -import { Views } from "@/lib/utils"; -import { useFormatedDate } from "@/hooks/use-formated-value"; -import { - AppEnv, - AuthProp, - ConfigField, - PageConfigModel, - SingleResource, - TranslationField, -} from "@/back/types/global"; +import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; +import RenderTranslationCell from "@/back/Components/Table/RenderTranslationCell"; +import { GetFormatedDate, GetTranslation, Views } from "@/lib/utils"; +import { AppEnv, AuthProp, SingleResource } from "@/back/types/global"; +import { ConfigField, PageConfigModel } from "@/back/types/dynamic-pages"; +import { TranslationField } from "@/back/types/i18n"; import str from "@/hooks/use-string"; import useTrans from "@/hooks/use-translations"; -import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; -import { renderTranslationCell } from "@/lib/translationCellFallback"; + +const getColumns = (appEnv: AppEnv) => { + return [ + { + accessorKey: "label", + header: str(GetTranslation("validation.custom.label")) + .ucFirst() + .value(), + cell: ({ value }: { value: TranslationField }) => ( + + ), + }, + { + accessorKey: "view", + header: str(GetTranslation("validation.custom.view")) + .ucFirst() + .value(), + cell: ({ value }: { value: string | null }) => {value}, + }, + { + accessorKey: "config", + header: str(GetTranslation("validation.custom.config")) + .ucFirst() + .value(), + cell: ({ value }: { value: TranslationField }) => + value[appEnv.locale] ? ( + + ) : ( +

+ {GetTranslation("back.table_not_known")} +

+ ), + }, + { + accessorKey: "created_at", + header: str(GetTranslation("validation.attributes.created_at")) + .ucFirst() + .value(), + cell: ({ value }: { value: string }) => ( + {GetFormatedDate(value)} + ), + }, + { + accessorKey: "updated_at", + header: str(GetTranslation("validation.attributes.updated_at")) + .ucFirst() + .value(), + cell: ({ value }: { value: string }) => ( + {GetFormatedDate(value)} + ), + }, + { + accessorKey: "deleted_at", + header: str(GetTranslation("validation.attributes.deleted_at")) + .ucFirst() + .value(), + cell: ({ value }: { value: string }) => ( + {GetFormatedDate(value)} + ), + }, + ]; +}; export default function Show({ data, @@ -46,60 +102,9 @@ export default function Show({ }), }, }); - const columns = [ - { - accessorKey: "label", - header: str(useTrans("validation.custom.label")).ucFirst().value(), - cell: ({ value }: { value: TranslationField }) => { - return renderTranslationCell(value); - }, - }, - { - accessorKey: "view", - header: str(useTrans("validation.custom.view")).ucFirst().value(), - cell: ({ value }: { value: string | null }) => {value}, - }, - { - accessorKey: "config", - header: str(useTrans("validation.custom.config")).ucFirst().value(), - cell: ({ value }: { value: TranslationField }) => - value[appEnv.locale] ? ( - - ) : ( -

- {useTrans("back.table_not_known")} -

- ), - }, - { - accessorKey: "created_at", - header: str(useTrans("validation.attributes.created_at")) - .ucFirst() - .value(), - cell: ({ value }: { value: string }) => ( - {useFormatedDate(value)} - ), - }, - { - accessorKey: "updated_at", - header: str(useTrans("validation.attributes.updated_at")) - .ucFirst() - .value(), - cell: ({ value }: { value: string }) => ( - {useFormatedDate(value)} - ), - }, - { - accessorKey: "deleted_at", - header: str(useTrans("validation.attributes.deleted_at")) - .ucFirst() - .value(), - cell: ({ value }: { value: string }) => ( - {useFormatedDate(value)} - ), - }, - ]; + const columns = getColumns(appEnv); + const transCrudBackToList = useTrans("back.crud_back_to_list"); const beforeNode = () => { return ( viewAny && ( @@ -107,9 +112,7 @@ export default function Show({ } - tooltipContent={ -

{useTrans("back.crud_back_to_list")}

- } + tooltipContent={

{transCrudBackToList}

} /> ) diff --git a/resources/ts/back/Pages/Users/Form.tsx b/resources/ts/back/Pages/Users/Form.tsx index 90c0764f..68d20bbc 100644 --- a/resources/ts/back/Pages/Users/Form.tsx +++ b/resources/ts/back/Pages/Users/Form.tsx @@ -60,6 +60,20 @@ export default function Form({ data, }); + const transAssociateToRole = str( + useTrans("back.form_associate_to", { + params: { + attribute: useTrans("validation.attributes.role"), + }, + choice: 1, + }), + ) + .ucFirst() + .value(); + const transFormTitleVisibility = useTrans("back.form_title_visibility"); + const transFormSubtitleVisibility = useTrans( + "back.form_subtitle_visibility", + ); return (
@@ -135,18 +149,7 @@ export default function Form({ ? filterRoles(model?.role) : enumUserRole } - label={str( - useTrans("back.form_associate_to", { - params: { - attribute: useTrans( - "validation.attributes.role", - ), - }, - choice: 1, - }), - ) - .ucFirst() - .value()} + label={transAssociateToRole} selected={data.role ? String(data.role) : undefined} setData={setData} /> @@ -155,9 +158,9 @@ export default function Form({ {isConceptor && ( } - description={useTrans("back.form_subtitle_visibility")} + description={transFormSubtitleVisibility} inline > { + return [ { accessorKey: "first_name", header: ({ column }: { column: { id: string } }) => ( ( ( ( ( )} {str( - useTrans( + GetTranslation( row.getValue("published") ? "back.yes" : "back.no", ), ) @@ -145,7 +135,9 @@ export default function Index({ accessorKey: "updated_at", header: ({ column }: { column: { id: string } }) => ( }) => ( - {useFormatedDate(row.getValue("updated_at"))} + {GetFormatedDate(row.getValue("updated_at"))} ), }, ]; +}; +export default function Index({ + userModels, + searchFields, +}: { + userModels: Resource; + searchFields: string; +}) { + const { auth, enumUserRole } = usePage().props as unknown as { + auth: AuthProp; + enumUserRole: LaravelEnum; + }; + const { create } = auth.policies.users; + const actionModels = [ + Views.VISUALIZATION, + Views.EDITION, + Views.DUPLICATION, + Views.DELETION, + ]; + + const columns = getColumns(enumUserRole); + const transCrudAddEntry = useTrans("back.crud_add_entry"); const afterNode = () => { return ( create && ( } - tooltipContent={

{useTrans("back.crud_add_entry")}

} + tooltipContent={

{transCrudAddEntry}

} /> ) ); diff --git a/resources/ts/back/Pages/Users/Show.tsx b/resources/ts/back/Pages/Users/Show.tsx index c70d3334..36dd723c 100644 --- a/resources/ts/back/Pages/Users/Show.tsx +++ b/resources/ts/back/Pages/Users/Show.tsx @@ -9,8 +9,7 @@ import AuthenticatedLayout from "@/back/Layouts/AuthenticatedLayout"; import DetailsTable from "@/back/Components/Table/DetailsTable"; import ModelActions from "@/back/Components/ModelActions"; import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; -import { Views } from "@/lib/utils"; -import { useFormatedDate } from "@/hooks/use-formated-value"; +import { GetFormatedDate, GetTranslation, Views } from "@/lib/utils"; import { AuthProp, LaravelEnum, @@ -20,30 +19,23 @@ import { import str from "@/hooks/use-string"; import useTrans from "@/hooks/use-translations"; -export default function Show({ data }: { data: SingleResource }) { - const { auth, enumUserRole } = usePage().props as unknown as { - auth: AuthProp; - enumUserRole: LaravelEnum; - }; - const { viewAny } = auth.policies.users; - const userModel = data.data as UserModel; - const actionModels = [Views.EDITION, Views.DUPLICATION, Views.DELETION]; - const columns = [ +const getColumns = (enumUserRole: LaravelEnum) => { + return [ { accessorKey: "first_name", - header: str(useTrans("validation.attributes.first_name")) + header: str(GetTranslation("validation.attributes.first_name")) .ucFirst() .value(), }, { accessorKey: "last_name", - header: str(useTrans("validation.attributes.last_name")) + header: str(GetTranslation("validation.attributes.last_name")) .ucFirst() .value(), }, { accessorKey: "email", - header: str(useTrans("validation.attributes.email")) + header: str(GetTranslation("validation.attributes.email")) .ucFirst() .value(), cell: ({ value }: { value: string }) => ( @@ -58,7 +50,7 @@ export default function Show({ data }: { data: SingleResource }) { }, { accessorKey: "role", - header: str(useTrans(`validation.attributes.role`)) + header: str(GetTranslation(`validation.attributes.role`)) .ucFirst() .value(), cell: ({ value }: { value: number }) => ( @@ -77,7 +69,7 @@ export default function Show({ data }: { data: SingleResource }) { }, { accessorKey: "published", - header: str(useTrans("validation.custom.published")) + header: str(GetTranslation("validation.custom.published")) .ucFirst() .value(), cell: ({ value }: { value: boolean }) => ( @@ -94,7 +86,7 @@ export default function Show({ data }: { data: SingleResource }) { ) : ( )} - {str(useTrans(value ? "back.yes" : "back.no")) + {str(GetTranslation(value ? "back.yes" : "back.no")) .ucFirst() .value()} @@ -102,33 +94,46 @@ export default function Show({ data }: { data: SingleResource }) { }, { accessorKey: "published_at", - header: str(useTrans("validation.custom.published_at")) + header: str(GetTranslation("validation.custom.published_at")) .ucFirst() .value(), cell: ({ value }: { value: string }) => ( - {useFormatedDate(value)} + {GetFormatedDate(value)} ), }, { accessorKey: "created_at", - header: str(useTrans("validation.attributes.created_at")) + header: str(GetTranslation("validation.attributes.created_at")) .ucFirst() .value(), cell: ({ value }: { value: string }) => ( - {useFormatedDate(value)} + {GetFormatedDate(value)} ), }, { accessorKey: "updated_at", - header: str(useTrans("validation.attributes.updated_at")) + header: str(GetTranslation("validation.attributes.updated_at")) .ucFirst() .value(), cell: ({ value }: { value: string }) => ( - {useFormatedDate(value)} + {GetFormatedDate(value)} ), }, ]; +}; +export default function Show({ data }: { data: SingleResource }) { + const { auth, enumUserRole } = usePage().props as unknown as { + auth: AuthProp; + enumUserRole: LaravelEnum; + }; + const { viewAny } = auth.policies.users; + const userModel = data.data as UserModel; + const actionModels = [Views.EDITION, Views.DUPLICATION, Views.DELETION]; + + const columns = getColumns(enumUserRole); + + const transCrudBackList = useTrans("back.crud_back_to_list"); const beforeNode = () => { return ( viewAny && ( @@ -136,9 +141,7 @@ export default function Show({ data }: { data: SingleResource }) { } - tooltipContent={ -

{useTrans("back.crud_back_to_list")}

- } + tooltipContent={

{transCrudBackList}

} /> ) diff --git a/resources/ts/back/app.tsx b/resources/ts/back/app.tsx index 43ba793b..8adf4861 100644 --- a/resources/ts/back/app.tsx +++ b/resources/ts/back/app.tsx @@ -1,3 +1,4 @@ +// * Packages import { createInertiaApp } from "@inertiajs/react"; import { resolvePageComponent } from "laravel-vite-plugin/inertia-helpers"; import { createRoot } from "react-dom/client"; diff --git a/resources/ts/back/types/activity-logs.d.ts b/resources/ts/back/types/activity-logs.d.ts new file mode 100644 index 00000000..c6f52dcf --- /dev/null +++ b/resources/ts/back/types/activity-logs.d.ts @@ -0,0 +1,16 @@ +// * Other +import { ModelPolicy, UserModel } from "@/back/types/global"; + +type ActivityLogModel = { + policies: ModelPolicy; + id: number; + model_class: string; + modifications: string | null; + user: UserModel | null; + user_id: number | null; + is_anonymous: boolean; + is_console: boolean; + model_id: number; + event: number; + created_at: Date; +}; diff --git a/resources/ts/back/types/dynamic-pages.d.ts b/resources/ts/back/types/dynamic-pages.d.ts new file mode 100644 index 00000000..587c8070 --- /dev/null +++ b/resources/ts/back/types/dynamic-pages.d.ts @@ -0,0 +1,76 @@ +// * Other +import { ModelPolicy } from "@/back/types/global"; +import { TranslationField } from "@/back/types/i18n"; + +// Dynamic Page type, with policies. +type DynamicPageModel = { + policies: ModelPolicy; + id: number; + id_key: string; + title: TranslationField; + slug: TranslationField; + content: TranslationField | null; + page_id: number; + config_id: number; + status: number; + isLocked: boolean; + isRoot: boolean; + created_at: Date; + updated_at: Date; + deleted_at: Date; +}; + +type DynamicFieldTypes = Record< + string, + { + label: string; + name: string; + value: number; + validation: string; + isarray: boolean; + } +>; + +type DynamicInternalField = { + id?: number; + value: string | number | boolean | File | Date; + order: number; +}; + +// Page config types +type ConfigField = { + key: string; + type: number; + label: string; + validation: string; + order: number; + children?: ConfigField[]; +}; + +type ConfigInternalField = Omit & { + id: number; + children?: ConfigInternalField[]; +}; + +type PageConfigModel = { + policies: ModelPolicy; + id: number; + label: TranslationField; + config: TranslationField; + view: string | null; + status: number; + created_at: Date; + updated_at: Date; + deleted_at: Date; +}; + +// Relations. +type PageConfigModelWithRelations = PageConfigModel & { + pages: DynamicPageModel[]; +}; + +type DynamicPageModelWithRelations = DynamicPageModel & { + config: PageConfigModel; + parent_page: DynamicPageModel | null; + children_page: DynamicPageModel[]; +}; diff --git a/resources/ts/back/types/global.d.ts b/resources/ts/back/types/global.d.ts index 4249983c..9a6a5053 100644 --- a/resources/ts/back/types/global.d.ts +++ b/resources/ts/back/types/global.d.ts @@ -12,9 +12,6 @@ type AppEnv = { fallbackLocale: string; }; -// Translation types. -type TranslationField = Record; - // Policy related types. type ModelPolicy = { view: boolean; @@ -23,6 +20,14 @@ type ModelPolicy = { duplicate: boolean; }; +// Default model, with policies. +type DefaultModel = { + policies: ModelPolicy; + id: number; + created_at: Date; + updated_at: Date; +}; + // User type, with policies. type UserModel = { policies: ModelPolicy; @@ -37,256 +42,6 @@ type UserModel = { updated_at: Date; }; -// Activity log type, with policies. -type ActivityLogModel = { - id: number; - model_class: string; - modifications: string | null; - user: UserModel | null; - user_id: number | null; - is_anonymous: boolean; - is_console: boolean; - model_id: number; - event: number; - created_at: Date; -}; - -// Dynamic Page type, with policies. -type DynamicPageModel = { - policies: ModelPolicy; - id: number; - id_key: string; - title: TranslationField; - slug: TranslationField; - content: TranslationField | null; - page_id: number; - config_id: number; - status: number; - isLocked: boolean; - isRoot: boolean; - created_at: Date; - updated_at: Date; - deleted_at: Date; - parent_page: DynamicPageModel | null; - children_page: DynamicPageModel[]; - config: PageConfigModel; -}; -type DynamicFieldTypes = Record< - string, - { - label: string; - name: string; - value: number; - validation: string; - isarray: boolean; - } ->; -type DynamicInternalField = { - id?: number; - value: string | number | boolean | File | Date | InternalValue[]; - order: number; -}; - -// Page config types -type PageConfigModel = { - policies: ModelPolicy; - id: number; - label: TranslationField; - config: TranslationField; - view: string | null; - status: number; - created_at: Date; - updated_at: Date; - deleted_at: Date; - pages: DynamicPageModel[]; -}; -type ConfigField = { - key: string; - type: number; - label: string; - validation: string; - order: number; - children?: ConfigField[]; -}; -type ConfigInternalField = Omit & { - id: number; - children?: ConfigInternalField[]; -}; - -// News type, with policies and relations. -type NewsModel = { - policies: ModelPolicy; - id: number; - seo_title: TranslationField | null; - seo_description: TranslationField | null; - title: TranslationField; - slug: TranslationField; - content: TranslationField; - inverted: boolean; - status: number; - vimeo_id: string | null; - has_show_picture: boolean; - picture: string | null; - picture_alt: TranslationField | null; - picture_title: TranslationField | null; - published_at: Date | null; - unpublished_at: Date | null; - created_at: Date; - updated_at: Date; - deleted_at: Date; - categories: NewsCategoryModel[]; - tags: NewsTagModel[]; -}; - -type NewsCategoryModel = { - policies: ModelPolicy; - id: number; - name: TranslationField; - slug: TranslationField; - news_count: number; - created_at: Date; - updated_at: Date; - deleted_at: Date; -}; - -type NewsTagModel = { - policies: ModelPolicy; - id: number; - name: TranslationField; - slug: TranslationField; - color: string; - news_count: number; - created_at: Date; - updated_at: Date; - deleted_at: Date; -}; - -type CompanyModel = { - id: number; - name: string; - logo: string; - responsable: string; - url: string; - informations: string; - employees: number; - latitude: number; - longitude: number; - activity_id: number; - sector_id: number | null; - published: boolean; - created_at: Date; - updated_at: Date; - deleted_at: Date; -}; - -type CompanyActivityModel = { - id: number; - name: TranslationField; - color: string; - published: boolean; - created_at: Date; - updated_at: Date; - deleted_at: Date; - companies: CompanyModel[]; -}; - -type CompanySectorModel = { - id: number; - name: TranslationField; - published: boolean; - created_at: Date; - updated_at: Date; - deleted_at: Date; - companies: CompanyModel[]; -}; -type DocumentModel = { - policies: ModelPolicy; - id: number; - name: TranslationField; - path: TranslationField | null; - is_issuu: boolean; - issuu_path: TranslationField | null; - status: number; - published_at: Date | null; - unpublished_at: Date | null; - exercice: number | null; - tags: DocumentTagModel[]; - categories: DocumentCategoryModel[]; - created_at: Date; - updated_at: Date; -}; - -type DocumentTagModel = { - id: number; - name: TranslationField; - slug: TranslationField; - color: string; - documents_count?: number; - created_at: Date; - updated_at: Date; -}; - -type DocumentCategoryModel = { - id: number; - name: TranslationField; - slug: TranslationField; - documents_count?: number; - created_at: Date; - updated_at: Date; -}; - -// Default model, with policies. -type DefaultModel = { - policies: ModelPolicy; - id: number; - created_at: Date; - updated_at: Date; -}; - -// All models types. -type Model = - | DefaultModel - | UserModel - | PageConfigModel - | DynamicPageModel - | NewsModel - | ActivityLogModel - | CompanyModel - | CompanyActivityModel - | CompanySectorModel - | DocumentTagModel - | DocumentCategoryModel - | ActivityLogModel - | NewsCategoryModel - | NewsTagModel; - -// Common policy type. -type CommonPolicy = { - viewAny: boolean; - viewTrash?: boolean; - create: boolean; -}; - -// ContactCount Type -type ContactCountModel = { - submissions: number; - type: string; -}; - -// Authentication props with user and policies -type AuthProp = { - user: UserModel; - policies: Record; -}; - -// View types used in the application. -type Views = { - label: string; - value: string; - policy: string; - icon: ElementType; -}[]; - // Pagination-related types. interface Links { first: string; @@ -303,7 +58,7 @@ interface PaginatorLink { interface Paginator { current_page: number; - data: Array; + data: Array; from: number; last_page: number; links: PaginatorLink[]; @@ -324,15 +79,36 @@ interface LaravelScout { totalPages: number; } +// Common policy type. +type CommonPolicy = { + viewAny: boolean; + viewTrash?: boolean; + create: boolean; +}; + +// Authentication props with user and policies +type AuthProp = { + user: UserModel; + policies: Record; +}; + +// View types used in the application. +type Views = { + label: string; + value: string; + policy: string; + icon: ElementType; +}[]; + // Resource-related types. type Resource = { - data: Model[]; + data: DefaultModel[]; link: Links; meta: Paginator; }; type SingleResource = { - data: Model; + data: DefaultModel; link: Links; meta: Paginator; }; @@ -343,17 +119,6 @@ type LaravelEnum = Record< { label: string; name: string; value: number } >; -// For Input file type -type FileValidation = { - validationRules?: { - width: string | null; - height: string | null; - } | null; - maxFileSize: string | null; - allowedFormats: string | null; - inputType?: number; -}; - // Theme type. type Theme = "dark" | "light"; diff --git a/resources/ts/back/types/i18n.d.ts b/resources/ts/back/types/i18n.d.ts new file mode 100644 index 00000000..bcd89e52 --- /dev/null +++ b/resources/ts/back/types/i18n.d.ts @@ -0,0 +1 @@ +export type TranslationField = Record; diff --git a/resources/ts/back/types/others.d.ts b/resources/ts/back/types/others.d.ts new file mode 100644 index 00000000..1a991104 --- /dev/null +++ b/resources/ts/back/types/others.d.ts @@ -0,0 +1,148 @@ +// * Other +import { ModelPolicy } from "@/back/types/global"; +import { TranslationField } from "@/back/types/i18n"; + +// News type, with policies and relations. +type NewsCategoryModel = { + policies: ModelPolicy; + id: number; + name: TranslationField; + slug: TranslationField; + news_count: number; + created_at: Date; + updated_at: Date; + deleted_at: Date; +}; + +type NewsTagModel = { + policies: ModelPolicy; + id: number; + name: TranslationField; + slug: TranslationField; + color: string; + news_count: number; + created_at: Date; + updated_at: Date; + deleted_at: Date; +}; + +type NewsModel = { + policies: ModelPolicy; + id: number; + seo_title: TranslationField | null; + seo_description: TranslationField | null; + title: TranslationField; + slug: TranslationField; + content: TranslationField; + inverted: boolean; + status: number; + vimeo_id: string | null; + has_show_picture: boolean; + picture: string | null; + picture_alt: TranslationField | null; + picture_title: TranslationField | null; + published_at: Date | null; + unpublished_at: Date | null; + created_at: Date; + updated_at: Date; + deleted_at: Date; + categories: NewsCategoryModel[]; + tags: NewsTagModel[]; +}; + +type CompanyModel = { + policies: ModelPolicy; + id: number; + name: string; + logo: string; + responsable: string; + url: string; + informations: string; + employees: number; + latitude: number; + longitude: number; + activity_id: number; + sector_id: number | null; + published: boolean; + created_at: Date; + updated_at: Date; + deleted_at: Date; +}; + +type CompanyActivityModel = { + policies: ModelPolicy; + id: number; + name: TranslationField; + color: string; + published: boolean; + created_at: Date; + updated_at: Date; + deleted_at: Date; + companies: CompanyModel[]; +}; + +type CompanySectorModel = { + policies: ModelPolicy; + id: number; + name: TranslationField; + published: boolean; + created_at: Date; + updated_at: Date; + deleted_at: Date; + companies: CompanyModel[]; +}; + +type DocumentCategoryModel = { + policies: ModelPolicy; + id: number; + name: TranslationField; + slug: TranslationField; + documents_count?: number; + created_at: Date; + updated_at: Date; +}; + +type DocumentTagModel = { + policies: ModelPolicy; + id: number; + name: TranslationField; + slug: TranslationField; + color: string; + documents_count?: number; + created_at: Date; + updated_at: Date; +}; + +type DocumentModel = { + policies: ModelPolicy; + id: number; + name: TranslationField; + path: TranslationField | null; + is_issuu: boolean; + issuu_path: TranslationField | null; + status: number; + published_at: Date | null; + unpublished_at: Date | null; + exercice: number | null; + tags: DocumentTagModel[]; + categories: DocumentCategoryModel[]; + created_at: Date; + updated_at: Date; +}; + +// ContactCount Type +type ContactCountModel = { + submissions: number; + type: string; +}; + +// For Input file type +type FileValidation = { + validationRules?: { + width: string | null; + height: string | null; + } | null; + maxFileSize: string | null; + allowedFormats: string | null; + inputType?: number; +}; diff --git a/resources/ts/front/base/common.ts b/resources/ts/front/base/common.ts index 17836696..59b0edc5 100644 --- a/resources/ts/front/base/common.ts +++ b/resources/ts/front/base/common.ts @@ -1,8 +1,11 @@ +/* eslint-disable */ + +// * Other import route from "../modules/route"; const laravelRoute = route.methods.route; -document.addEventListener("DOMContentLoaded", function () { +document.addEventListener("DOMContentLoaded", () => { // * NEWSLETTER const newsletterSubmitBtn = document.getElementById("newsletter-submit"); const newsletterEmailInput = document.getElementById( @@ -11,7 +14,7 @@ document.addEventListener("DOMContentLoaded", function () { const newsletterMessageDiv = document.getElementById("newsletter-message"); if (newsletterSubmitBtn && newsletterEmailInput && newsletterMessageDiv) { // Submit the request - newsletterSubmitBtn.addEventListener("click", function () { + newsletterSubmitBtn.addEventListener("click", () => { const addSingleOptinRoute = laravelRoute("front.add-single-optin"); if (addSingleOptinRoute === null) { return; diff --git a/resources/ts/front/index.d.ts b/resources/ts/front/index.d.ts index 170f533b..12ec48b1 100644 --- a/resources/ts/front/index.d.ts +++ b/resources/ts/front/index.d.ts @@ -1,3 +1,4 @@ +// * Packages import type CookieNotice from "@ldc-web/cookie-notice"; import { Chart } from "chart.js"; diff --git a/resources/ts/front/modules/route.ts b/resources/ts/front/modules/route.ts index adf2d26c..d020d696 100644 --- a/resources/ts/front/modules/route.ts +++ b/resources/ts/front/modules/route.ts @@ -1,3 +1,5 @@ +/* eslint-disable */ + const route = { methods: { /** diff --git a/resources/ts/hooks/use-crud-messages.tsx b/resources/ts/hooks/use-crud-messages.tsx index e0ef1651..2012161b 100644 --- a/resources/ts/hooks/use-crud-messages.tsx +++ b/resources/ts/hooks/use-crud-messages.tsx @@ -1,6 +1,3 @@ -// * Packages -import { useMemo } from "react"; -// * Other import { Methods } from "@/lib/enums"; import useTrans from "@/hooks/use-translations"; import str from "@/hooks/use-string"; @@ -16,64 +13,53 @@ export default function useCrudMessages({ modelName, modelNameTrans, }: UseCrudMessagesParams) { - return useMemo(() => { - const isPost = method === Methods.POST; + const isPost = method === Methods.POST; - const titleSuccess = useTrans( - isPost - ? "crud.messages.successful_creation" - : "crud.messages.successful_edition", - ); + // Translations. + const keyTitleSuccess = isPost + ? "crud.messages.successful_creation" + : "crud.messages.successful_edition"; + const keyDescriptionSuccess = isPost + ? "crud.messages.has_been_created" + : "crud.messages.has_been_updated"; + const keyTitleError = isPost + ? "crud.messages.canceled_creation" + : "crud.messages.canceled_edition"; + const keyDescriptionError = isPost + ? "crud.messages.cannot_be_created" + : "crud.messages.cannot_be_updated"; - const descriptionSuccess = str( - useTrans( - isPost - ? "crud.messages.has_been_created" - : "crud.messages.has_been_updated", - { - params: { - model: - modelNameTrans ?? - useTrans(`models.classes.${modelName}`, { - choice: 1, - }), - }, - }, - ), - ) - .ucFirst() - .value(); + const modelNameTranslation = useTrans(`models.classes.${modelName}`, { + choice: 1, + }); + // Hooks appelés **toujours dans le même ordre** + const titleSuccess = useTrans(keyTitleSuccess); + const descriptionSuccess = str( + useTrans(keyDescriptionSuccess, { + params: { + model: modelNameTrans ?? modelNameTranslation, + }, + }), + ) + .ucFirst() + .value(); - const titleError = useTrans( - isPost - ? "crud.messages.canceled_creation" - : "crud.messages.canceled_edition", - ); + const titleError = useTrans(keyTitleError); - const descriptionError = str( - useTrans( - isPost - ? "crud.messages.cannot_be_created" - : "crud.messages.cannot_be_updated", - { - params: { - model: - modelNameTrans ?? - useTrans(`models.classes.${modelName}`, { - choice: 1, - }), - }, - }, - ), - ) - .ucFirst() - .value(); + const descriptionError = str( + useTrans(keyDescriptionError, { + params: { + model: modelNameTrans ?? modelNameTranslation, + }, + }), + ) + .ucFirst() + .value(); - return { - titleSuccess, - descriptionSuccess, - titleError, - descriptionError, - }; - }, [method, modelName, useTrans, str]); + return { + titleSuccess, + descriptionSuccess, + titleError, + descriptionError, + }; } diff --git a/resources/ts/hooks/use-crud-submit.tsx b/resources/ts/hooks/use-crud-submit.tsx index b3830be5..d6aeea93 100644 --- a/resources/ts/hooks/use-crud-submit.tsx +++ b/resources/ts/hooks/use-crud-submit.tsx @@ -2,24 +2,31 @@ import { FormEvent, useCallback } from "react"; import { route } from "ziggy-js"; import { usePage } from "@inertiajs/react"; -import { AppEnv } from "@/back/types/global"; +import type { VisitOptions } from "@inertiajs/core"; // * Other import { Methods } from "@/lib/enums"; import useCrudMessages from "@/hooks/use-crud-messages"; import { useToast } from "@/hooks/use-toast"; +import { AppEnv } from "@/back/types/global"; -interface UseCrudSubmitParams { +interface CustomData { + _method?: "post" | "put"; + locale?: string; +} + +interface UseCrudSubmitParams { method: Methods; modelRoute: string; modelName?: string; action: string; model?: { id?: number | string }; - post: (url: string, options: any) => void; + // eslint-disable-next-line no-unused-vars + post: (url: string, options?: VisitOptions) => void; modelNameTrans?: string; - data: any; + data: TFormData & CustomData; } -export default function useCrudSubmit({ +export default function useCrudSubmit({ method, modelRoute, modelName, @@ -27,8 +34,8 @@ export default function useCrudSubmit({ model, post, data, - modelNameTrans -}: UseCrudSubmitParams) { + modelNameTrans, +}: UseCrudSubmitParams) { const { locale } = usePage().props.appEnv as AppEnv; /** Set messages success/errors */ const messages = useCrudMessages({ method, modelName, modelNameTrans }); @@ -41,7 +48,9 @@ export default function useCrudSubmit({ throw new Error("The method must be post or patch"); } - data._method = method === Methods.POST ? "post" : "put" + // eslint-disable-next-line no-underscore-dangle, no-param-reassign + data._method = method === Methods.POST ? "post" : "put"; + // eslint-disable-next-line no-param-reassign data.locale = locale; const routeUrl = route(`back.${modelRoute}.${action}`, model?.id); @@ -62,6 +71,19 @@ export default function useCrudSubmit({ }, }); }, - [method, modelRoute, action, model, post], + [ + method, + modelRoute, + action, + model, + post, + data, + locale, + messages.titleSuccess, + messages.descriptionSuccess, + messages.titleError, + messages.descriptionError, + toast, + ], ); } diff --git a/resources/ts/hooks/use-recaptcha.tsx b/resources/ts/hooks/use-recaptcha.tsx index edf5373d..f0551bd1 100644 --- a/resources/ts/hooks/use-recaptcha.tsx +++ b/resources/ts/hooks/use-recaptcha.tsx @@ -1,7 +1,10 @@ +// * Packages import { ReactNode, useEffect, useState } from "react"; -import useTrans from "@/hooks/use-translations"; -import { Button } from "@/components/ui/button"; import { InfoIcon } from "lucide-react"; +// * Components shadcn +import { Button } from "@/components/ui/button"; +// * Other +import useTrans from "@/hooks/use-translations"; declare global { interface Window { diff --git a/resources/ts/lib/mapEnumOptions.ts b/resources/ts/lib/mapEnumOptions.ts index eb199d2d..2672697f 100644 --- a/resources/ts/lib/mapEnumOptions.ts +++ b/resources/ts/lib/mapEnumOptions.ts @@ -1,8 +1,11 @@ +// * Packages import { LaravelEnum } from "@/back/types/global"; -export function mapLaravelEnumOptions< +type AllowedValue = string | boolean | Date | number | File; + +export default function mapLaravelEnumOptions< T extends LaravelEnum, - D extends Record, + D extends Record, >(enumObject: T, data?: D[], valueKey?: keyof D) { const options = Object.values(enumObject).map((opt) => ({ value: opt.value.toString(), diff --git a/resources/ts/lib/mapPivotOptions.ts b/resources/ts/lib/mapPivotOptions.ts index c67f9d98..96bd8be6 100644 --- a/resources/ts/lib/mapPivotOptions.ts +++ b/resources/ts/lib/mapPivotOptions.ts @@ -1,63 +1,39 @@ +// * Other import { Model } from "@/back/types/global"; /** - * Function allowing to extracts Ids from the model relation pivot and format given options to - * works in the multi select component. - * - * @param model T - * @param data D[] - * @param valueKey keyof D - * @param locale string | null - * @returns + * Extract ids from model relation pivot and format options + * for a multi select component. */ -export function mapPivotOptions< - T extends Model[], - D extends Record, ->( - /** The model which we want to select options */ - model: T, - /** Data from the database */ +export function mapPivotOptions( + model: T[], data?: D[], - /** The key name where we find the label of the model */ - valueKey?: keyof D, - /** If we have a translatable field we pass the current locale */ + valueKey?: keyof T, locale?: string | null, ) { - const options = - model && model.length - ? model.map((opt) => { - const raw = opt[ - valueKey as keyof typeof opt - ] as unknown as string; - return { - value: opt.id.toString(), - label: - locale && typeof raw === "object" ? raw[locale] : raw, - }; - }) - : []; + const options = model?.length + ? model.map((opt) => { + const raw = opt[valueKey as keyof T] as unknown; + + return { + value: opt.id.toString(), + label: + locale && typeof raw === "object" && raw !== null + ? (raw as Record)[locale] + : (raw as string), + }; + }) + : []; + + const defaultValue = data?.length + ? data.map((item) => item.id.toString()) + : []; - const defaultValue: string[] = - data && valueKey - ? data.map((item) => { - return item.id.toString(); - }) - : []; return { options, defaultValue }; } -/** - * Function allowing to extracts Ids from the model relation pivot. - * - * @param data - * @returns string[]|[] - */ -export function extractIdsFromDatabasePivotRelation< - D extends Record, ->(data?: D[]): number[] | [] { - return data - ? data.map((item) => { - return item.id; - }) - : []; +export function extractIdsFromDatabasePivotRelation( + data?: D[], +): number[] { + return data ? data.map((item) => item.id) : []; } diff --git a/resources/ts/lib/utils.ts b/resources/ts/lib/utils.ts index d41aa0be..cd6f5054 100644 --- a/resources/ts/lib/utils.ts +++ b/resources/ts/lib/utils.ts @@ -1,4 +1,6 @@ // * Packages +import { useFormatedDate } from "@/hooks/use-formated-value"; +import useTrans, { UseTransOptions } from "@/hooks/use-translations"; import { clsx, type ClassValue } from "clsx"; import { ArchiveIcon, @@ -20,6 +22,32 @@ export function cn(...inputs: ClassValue[]): string { return twMerge(clsx(inputs)); } +/** + * Get the translation value. + * + * @param string name + * @return string + */ +export const GetTranslation = (keyName: string, options?: UseTransOptions) => { + return useTrans(keyName, options); +}; + +/** + * Get the formated date. + * + * @param string date + * @param string timezone + * @param boolean time + * @returns + */ +export const GetFormatedDate = ( + date: string, + timezone?: string, + time?: boolean, +) => { + return useFormatedDate(date, timezone, time); +}; + /** * List all actions on back-end models. * @return object diff --git a/routes/web-back.php b/routes/web-back.php index 8d65a91b..96e29e9e 100644 --- a/routes/web-back.php +++ b/routes/web-back.php @@ -149,6 +149,8 @@ Route::prefix('admin') // * DOCUMENTS. Route::resource('documents', DocumentController::class); + Route::get('/documents/{document}/duplicate', [DocumentController::class, 'duplicate']) + ->name('documents.duplicate'); // * DOCUMENTS CATEGORIES. Route::get('/document-categories/trash', [DocumentCategoryController::class, 'index'])