diff --git a/app/Http/Controllers/Back/BrandController.php b/app/Http/Controllers/Back/BrandController.php index 4deeec0..98875f5 100644 --- a/app/Http/Controllers/Back/BrandController.php +++ b/app/Http/Controllers/Back/BrandController.php @@ -6,6 +6,7 @@ use App\Http\Requests\Back\Brands\StoreBrandRequest; use App\Http\Requests\Back\Brands\UpdateBrandRequest; use App\Models\Brand; use App\Models\Site; +use Illuminate\Database\Eloquent\SoftDeletingScope; use Illuminate\Http\Request; use Illuminate\Support\Arr; use Illuminate\Support\Facades\DB; @@ -101,7 +102,9 @@ class BrandController extends BackendController public function show(Brand $brand): \Inertia\Response { return $this->inertiaRender("back/Pages/$this->modelPage/Show", [ - 'data' => CommonResource::make($brand->load(['sites'])), + 'data' => CommonResource::make($brand->load([ + 'sites' => fn($q) => $q->withoutGlobalScope(SoftDeletingScope::class), + ])), ]); } @@ -183,7 +186,10 @@ class BrandController extends BackendController */ public function duplicate(Brand $brand): \Inertia\Response { - return $this->create($brand->replicate()); + $brand->loadMissing(Brand::$cacheRelations ?? []); + $replica = $brand->replicate(); + collect($brand->getRelations())->each(fn($value, $relation) => $replica->setRelation($relation, $value)); + return $this->create($replica); } /** diff --git a/app/Http/Controllers/Back/GitlabProjectController.php b/app/Http/Controllers/Back/GitlabProjectController.php index 6115748..1f071bc 100644 --- a/app/Http/Controllers/Back/GitlabProjectController.php +++ b/app/Http/Controllers/Back/GitlabProjectController.php @@ -167,7 +167,10 @@ class GitlabProjectController extends BackendController */ public function duplicate(GitlabProject $gitlab_project): \Inertia\Response { - return $this->create($gitlab_project->replicate()); + $gitlab_project->loadMissing(GitlabProject::$cacheRelations ?? []); + $replica = $gitlab_project->replicate(); + collect($gitlab_project->getRelations())->each(fn($value, $relation) => $replica->setRelation($relation, $value)); + return $this->create($replica); } /** diff --git a/app/Http/Controllers/Back/InvoiceCompanyController.php b/app/Http/Controllers/Back/InvoiceCompanyController.php index 6b1c432..568fc37 100644 --- a/app/Http/Controllers/Back/InvoiceCompanyController.php +++ b/app/Http/Controllers/Back/InvoiceCompanyController.php @@ -167,7 +167,10 @@ class InvoiceCompanyController extends BackendController */ public function duplicate(InvoiceCompany $invoice_company): \Inertia\Response { - return $this->create($invoice_company->replicate()); + $invoice_company->loadMissing(InvoiceCompany::$cacheRelations ?? []); + $replica = $invoice_company->replicate(); + collect($invoice_company->getRelations())->each(fn($value, $relation) => $replica->setRelation($relation, $value)); + return $this->create($replica); } /** diff --git a/app/Http/Controllers/Back/InvoicePackageController.php b/app/Http/Controllers/Back/InvoicePackageController.php index 7b9df64..f7cdfb5 100644 --- a/app/Http/Controllers/Back/InvoicePackageController.php +++ b/app/Http/Controllers/Back/InvoicePackageController.php @@ -167,7 +167,10 @@ class InvoicePackageController extends BackendController */ public function duplicate(InvoicePackage $invoice_package): \Inertia\Response { - return $this->create($invoice_package->replicate()); + $invoice_package->loadMissing(InvoicePackage::$cacheRelations ?? []); + $replica = $invoice_package->replicate(); + collect($invoice_package->getRelations())->each(fn($value, $relation) => $replica->setRelation($relation, $value)); + return $this->create($replica); } /** diff --git a/app/Http/Controllers/Back/NoticeController.php b/app/Http/Controllers/Back/NoticeController.php index 7a2e878..c7bf2cb 100644 --- a/app/Http/Controllers/Back/NoticeController.php +++ b/app/Http/Controllers/Back/NoticeController.php @@ -167,7 +167,10 @@ class NoticeController extends BackendController */ public function duplicate(Notice $notice): \Inertia\Response { - return $this->create($notice->replicate()); + $notice->loadMissing(Notice::$cacheRelations ?? []); + $replica = $notice->replicate(); + collect($notice->getRelations())->each(fn($value, $relation) => $replica->setRelation($relation, $value)); + return $this->create($replica); } /** diff --git a/app/Http/Controllers/Back/SiteController.php b/app/Http/Controllers/Back/SiteController.php index 9b610ac..41f4539 100644 --- a/app/Http/Controllers/Back/SiteController.php +++ b/app/Http/Controllers/Back/SiteController.php @@ -4,9 +4,9 @@ namespace App\Http\Controllers\Back; use App\Http\Requests\Back\Sites\StoreSiteRequest; use App\Http\Requests\Back\Sites\UpdateSiteRequest; -use App\Models\GitlabProject; use App\Models\Site; use App\Models\User; +use Illuminate\Database\Eloquent\SoftDeletingScope; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; use Illuminate\Support\Arr; @@ -34,13 +34,6 @@ class SiteController extends BackendController */ private \Illuminate\Support\Collection $userModelsClient; - /** - * Gitlab projects collection. - * - * @var \Illuminate\Support\Collection - */ - private \Illuminate\Support\Collection $gitlabProjectModels; - /** * Create the controller instance. */ @@ -59,7 +52,6 @@ class SiteController extends BackendController $this->userModelsClient = $userModels ->filter(fn(User $user) => $user->role->value === UserRoleRegistry::get('client')->value) ->values(); - $this->gitlabProjectModels = GitlabProject::getModelsCached(); } /** @@ -117,7 +109,6 @@ class SiteController extends BackendController 'data' => CommonResource::make($site), 'userModelsProjectManager' => $this->userModelsProjectManager, 'userModelsClient' => $this->userModelsClient, - 'gitlabProjectModels' => $this->gitlabProjectModels, ]); } @@ -130,10 +121,13 @@ class SiteController extends BackendController public function show(Site $site): \Inertia\Response { return $this->inertiaRender("back/Pages/$this->modelPage/Show", [ - 'data' => CommonResource::make($site->load(['projectManager', 'gitlabProject', 'users'])), + 'data' => CommonResource::make($site->load([ + 'projectManager' => fn($q) => $q->withoutGlobalScope(SoftDeletingScope::class), + 'gitlabProject' => fn($q) => $q->withoutGlobalScope(SoftDeletingScope::class), + 'users' => fn($q) => $q->withoutGlobalScope(SoftDeletingScope::class), + ])), 'userModelsProjectManager' => $this->userModelsProjectManager, 'userModelsClient' => $this->userModelsClient, - 'gitlabProjectModels' => $this->gitlabProjectModels, ]); } @@ -173,10 +167,13 @@ class SiteController extends BackendController public function edit(Site $site): \Inertia\Response { return $this->inertiaRender("back/Pages/$this->modelPage/Edit", [ - 'data' => CommonResource::make($site->load(['projectManager', 'gitlabProject', 'users'])), + 'data' => CommonResource::make($site->load([ + 'projectManager' => fn($q) => $q->withoutGlobalScope(SoftDeletingScope::class), + 'gitlabProject' => fn($q) => $q->withoutGlobalScope(SoftDeletingScope::class), + 'users' => fn($q) => $q->withoutGlobalScope(SoftDeletingScope::class), + ])), 'userModelsProjectManager' => $this->userModelsProjectManager, 'userModelsClient' => $this->userModelsClient, - 'gitlabProjectModels' => $this->gitlabProjectModels, ]); } @@ -217,7 +214,10 @@ class SiteController extends BackendController */ public function duplicate(Site $site): \Inertia\Response { - return $this->create($site->replicate()); + $site->loadMissing(Site::$cacheRelations ?? []); + $replica = $site->replicate(); + collect($site->getRelations())->each(fn($value, $relation) => $replica->setRelation($relation, $value)); + return $this->create($replica); } /** diff --git a/app/Models/Brand.php b/app/Models/Brand.php index 1a137d7..9aa6a8e 100644 --- a/app/Models/Brand.php +++ b/app/Models/Brand.php @@ -45,6 +45,15 @@ class Brand extends Model 'name', ]; + /** + * Relations should be included in the cache. + * + * @var list + */ + public static $cacheRelations = [ + 'sites', + ]; + // * RELATIONS. /** diff --git a/app/Models/Site.php b/app/Models/Site.php index fd63cf9..668ef20 100644 --- a/app/Models/Site.php +++ b/app/Models/Site.php @@ -58,9 +58,10 @@ class Site extends Model * * @var list */ - protected static $cacheRelations = [ + public static $cacheRelations = [ 'projectManager', 'gitlabProject', + 'users', ]; /** diff --git a/app/Models/User.php b/app/Models/User.php index 42b2163..521d520 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -15,6 +15,16 @@ use Uwil\LaravelBackend\Models\User as BackendUser; */ class User extends BackendUser { + /** + * Relations should be included in the cache. + * + * @var list + */ + public static $cacheRelations = [ + 'userPassword', + 'sites', + ]; + /** * Get the password for authentication. * Retrieves the hashed password from the related user_passwords table. @@ -58,4 +68,20 @@ class User extends BackendUser { return $this->hasOne(UserPassword::class); } + + /** + * Get sites associated to the brand. + * + * @return \Illuminate\Database\Eloquent\Relations\MorphToMany<\App\Models\Site> + */ + public function sites(): \Illuminate\Database\Eloquent\Relations\MorphToMany + { + return $this->morphToMany( + Site::class, + 'sitable', + 'sitables', + 'sitable_id', + 'site_id', + )->withPivot('created_at'); + } } diff --git a/resources/ts/back/Components/Inputs/ModelSelect.tsx b/resources/ts/back/Components/Inputs/ModelSelect.tsx index 0712bd3..d778fef 100644 --- a/resources/ts/back/Components/Inputs/ModelSelect.tsx +++ b/resources/ts/back/Components/Inputs/ModelSelect.tsx @@ -320,10 +320,16 @@ interface ModelSelectProps /** * Make the Close button visible - * Optional, defaults to empty + * Optional, defaults to true */ closeButtonVisible?: boolean; + /** + * Make the Empty button visible + * Optional, defaults to true + */ + emptyButtonVisible?: boolean; + /** * Model name * Take the name of the model to fetch in the backend, using ajax function @@ -404,7 +410,8 @@ export const ModelSelect = forwardRef( deduplicateOptions = false, resetOnDefaultValueChange = true, closeOnSelect = false, - closeButtonVisible = false, + closeButtonVisible = true, + emptyButtonVisible = true, targetModel, field, exclude = null, @@ -1228,7 +1235,7 @@ export const ModelSelect = forwardRef( > {selectedValues.length === 0 && (
-

+

{placeholder}

@@ -1611,10 +1618,10 @@ export const ModelSelect = forwardRef(
)} - {(closeButtonVisible || selectedValues.length > 0) && ( + {(closeButtonVisible || (emptyButtonVisible && selectedValues.length > 0)) && (
- {selectedValues.length > 0 && ( + {emptyButtonVisible && selectedValues.length > 0 && ( filterActions({ diff --git a/resources/ts/back/Pages/Brands/Form.tsx b/resources/ts/back/Pages/Brands/Form.tsx index 6c56abf..b58516e 100644 --- a/resources/ts/back/Pages/Brands/Form.tsx +++ b/resources/ts/back/Pages/Brands/Form.tsx @@ -1,20 +1,18 @@ // * Packages import { useForm, usePage } from "@inertiajs/react"; import { BookTextIcon } from "lucide-react"; -// * Components shadcn -// // * Other import FieldsetWrapper from "@/back/Components/FieldsetWrapper"; import SimpleString from "@/back/Components/Inputs/SimpleString"; +import { ModelSelect } from "@/back/Components/Inputs/ModelSelect"; import useTrans from "@/hooks/use-translations"; import InputError from "@/back/Components/InputError"; import SubmitButton from "@/back/Components/SubmitButton"; import useCrudSubmit from "@/back/hooks/use-crud-submit"; -import { BrandModel, SiteModel } from "@/back/types/project"; +import { BrandModel } from "@/back/types/project"; import str from "@/hooks/use-string"; import { AppEnv } from "@/types/global"; -import { MultiSelect } from "@/back/Components/Inputs/MultiSelect"; -import { extractIdsFromDatabasePivotRelation, mapPivotOptions } from "@/lib/mapPivotOptions"; +import { extractIdsFromDatabasePivotRelation } from "@/lib/mapPivotOptions"; export default function Form({ model, @@ -29,12 +27,8 @@ export default function Form({ action: string; method: number; }) { - const { - appEnv, - siteModels - } = usePage().props as unknown as { + const { appEnv } = usePage().props as unknown as { appEnv: AppEnv; - siteModels: Array; }; /** Form section */ @@ -56,8 +50,6 @@ export default function Form({ }), }); - const selectOptionsSites = mapPivotOptions(siteModels, model?.sites, 'name', appEnv.locale); - return (
@@ -83,8 +75,10 @@ export default function Form({ value={data.name} setData={setData} /> -
diff --git a/resources/ts/back/Pages/Brands/Show.tsx b/resources/ts/back/Pages/Brands/Show.tsx index 75cb060..895bb42 100644 --- a/resources/ts/back/Pages/Brands/Show.tsx +++ b/resources/ts/back/Pages/Brands/Show.tsx @@ -1,9 +1,14 @@ // * Packages import { usePage } from "@inertiajs/react"; import { route } from "ziggy-js"; -import { ArrowLeftIcon } from "lucide-react"; +import { ArrowLeftIcon, GlobeIcon, TriangleAlertIcon } from "lucide-react"; // * Components shadcn import { Badge } from "@/components/ui/badge"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/tooltip"; // * Other import AuthenticatedLayout from "@/back/Layouts/AuthenticatedLayout"; import DetailsTable from "@/back/Components/Table/DetailsTable"; @@ -16,8 +21,10 @@ import str from "@/hooks/use-string"; import useTrans from "@/hooks/use-translations"; import { TranslationField } from "@/back/types/i18n"; import RenderTranslationCell from "@/back/Components/Table/RenderTranslationCell"; +import { SiteModel } from "@/back/types/project"; +import { AppEnv } from "@/types/global"; -const getColumns = () => { +const getColumns = (appEnv: AppEnv, viewAnySites: boolean) => { return [ { accessorKey: "name", @@ -26,6 +33,97 @@ const getColumns = () => { ), }, + { + accessorKey: "sites", + header: str(GetTranslation("models.classes.sites", { choice: 2 })) + .ucFirst() + .value(), + cell: ({ value }: { value: Array }) => { + if (value.length) { + const projectData = (siteModel: SiteModel) => ( + {siteModel.name[appEnv.locale]} + ); + return ( +
+ {value.map((siteModel) => { + let content = ( +
+ {projectData(siteModel)} + {siteModel.deleted_at !== null && ( + + + + + + {GetTranslation("back.model_archived", { + params: { + model: str( + GetTranslation( + "models.classes.gitlab-projects", + { + choice: 1, + }, + ), + ) + .ucFirst() + .value(), + }, + choice: 1, + })} + + + )} +
+ ); + if (viewAnySites && siteModel.deleted_at === null) { + content = ( + + + {projectData(siteModel)} + + } + tooltipContent={ +

+ {GetTranslation("uwil-backend::back.access_model", { + params: { + model: GetTranslation( + "models.classes.sites", + { + choice: 1, + }, + ), + }, + })} +

+ } + type="button" + size="default" + variant="secondary" + /> + ); + } + return ( +
+ {content} +
+ ); + })} +
+ ); + } + return ( +

+ {GetTranslation("uwil-backend::back.table_not_known")} +

+ ); + }, + }, { accessorKey: "created_at", header: str( @@ -69,10 +167,12 @@ export default function Show({ data: SingleResource; modelName: string; }) { - const { auth } = usePage().props as unknown as { + const { auth, appEnv } = usePage().props as unknown as { auth: AuthProp; + appEnv: AppEnv; }; const { viewAny } = auth.policies.brands; + const { viewAny: viewAnySites } = auth.policies.sites; const model = data.data as DefaultModel; const actionModels = [Views.EDITION, Views.DUPLICATION, Views.ARCHIVATION]; const transTitle = useTrans( @@ -85,7 +185,7 @@ export default function Show({ }, }, ); - const columns = getColumns(); + const columns = getColumns(appEnv, viewAnySites); // Translations. const transCrudBackToList = useTrans("uwil-backend::back.crud_back_to_list"); diff --git a/resources/ts/back/Pages/Sites/Create.tsx b/resources/ts/back/Pages/Sites/Create.tsx index b3a60f9..5a2c168 100644 --- a/resources/ts/back/Pages/Sites/Create.tsx +++ b/resources/ts/back/Pages/Sites/Create.tsx @@ -8,7 +8,8 @@ import Form from "@/back/Pages/Sites/Form"; import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; import useTrans from "@/hooks/use-translations"; import { Actions, Methods } from "@/back/lib/enums"; -import { AuthProp, DefaultModel, SingleResource } from "@/back/types/global"; +import { AuthProp, SingleResource } from "@/back/types/global"; +import { SiteModel } from "@/back/types/project"; export default function Create({ data, @@ -18,7 +19,7 @@ export default function Create({ modelName: string; }) { const { viewAny } = (usePage().props.auth as AuthProp).policies.sites; - const model = data.data as DefaultModel; + const model = data.data as SiteModel; // Translations. const transBackToList = useTrans("uwil-backend::back.crud_back_to_list"); diff --git a/resources/ts/back/Pages/Sites/Edit.tsx b/resources/ts/back/Pages/Sites/Edit.tsx index 334c1d0..5c2fa48 100644 --- a/resources/ts/back/Pages/Sites/Edit.tsx +++ b/resources/ts/back/Pages/Sites/Edit.tsx @@ -9,8 +9,9 @@ import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithT import Form from "@/back/Pages/Sites/Form"; import useTrans from "@/hooks/use-translations"; import { Actions, Methods } from "@/back/lib/enums"; -import { Views } from "@/back/lib/actions"; -import { AuthProp, DefaultModel, SingleResource } from "@/back/types/global"; +import Views from "@/back/lib/actions"; +import { AuthProp, SingleResource } from "@/back/types/global"; +import { SiteModel } from "@/back/types/project"; export default function Edit({ data, @@ -20,7 +21,7 @@ export default function Edit({ modelName: string; }) { const { viewAny } = (usePage().props.auth as AuthProp).policies.sites; - const model = data.data as DefaultModel; + const model = data.data as SiteModel; const actionModels = [ Views.VISUALIZATION, Views.DUPLICATION, diff --git a/resources/ts/back/Pages/Sites/Form.tsx b/resources/ts/back/Pages/Sites/Form.tsx index 7f9a0e5..3a44acd 100644 --- a/resources/ts/back/Pages/Sites/Form.tsx +++ b/resources/ts/back/Pages/Sites/Form.tsx @@ -14,10 +14,11 @@ import { } from "@/back/Components/Inputs/MultiSelect"; import { UserModel } from "@/back/types/global"; import str from "@/hooks/use-string"; -import { GitlabProjectModel, SiteModel } from "@/back/types/project"; +import { SiteModel } from "@/back/types/project"; import { AppEnv } from "@/types/global"; import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; import { GetTranslation, isAValidUrl } from "@/lib/utils"; +import { ModelSelect } from "@/back/Components/Inputs/ModelSelect"; export default function Form({ model, @@ -32,25 +33,20 @@ export default function Form({ action: string; method: number; }) { - const { - appEnv, - userModelsProjectManager, - userModelsClient, - gitlabProjectModels, - } = usePage().props as unknown as { + const { appEnv, userModelsProjectManager, userModelsClient } = usePage() + .props as unknown as { appEnv: AppEnv; userModelsProjectManager: Array; userModelsClient: Array; - gitlabProjectModels: Array; }; /** Form section */ const { post, setData, data } = useForm({ name: model?.name ? model?.name[appEnv.locale] : undefined, - url_re7: model?.url_re7 ?? undefined, - url_prod: model?.url_prod ?? undefined, - project_manager_id: model?.project_manager_id ?? undefined, - gitlab_project_id: model?.gitlab_project_id ?? undefined, + url_re7: model?.url_re7, + url_prod: model?.url_prod, + project_manager_id: model?.project_manager_id, + gitlab_project_id: model?.gitlab_project_id, users: model && model.users ? model?.users?.map((user) => String(user.id)) @@ -99,10 +95,6 @@ export default function Form({ userModelsClient, (userModel) => `${userModel.first_name} ${userModel.last_name}`, ); - const selectOptionsProject = formatSelectOptions( - gitlabProjectModels, - (gitlabProjectModel) => gitlabProjectModel.name[appEnv.locale], - ); return ( @@ -155,9 +147,11 @@ export default function Form({ options={selectOptionsProjectManager} multiple={false} defaultValue={ - data.project_manager_id && String(data.project_manager_id) !== "" + model?.project_manager?.deleted_at === null && + data.project_manager_id && + String(data.project_manager_id) !== "" ? [String(data.project_manager_id)] - : [] + : undefined } setData={setData} maxSelection={1} @@ -193,10 +187,12 @@ export default function Form({ options={selectOptionsClient} defaultValue={data.users} setData={setData} - closeButtonVisible + closeButtonVisible={true} /> - { ); }, }, + { + accessorKey: "users", + header: str( + GetTranslation("uwil-backend::models.classes.users", { choice: 2 }), + ) + .ucFirst() + .value(), + cell: ({ value }: { value: Array }) => { + if (value.length) { + const projectData = (userModel: UserModel) => ( + + {userModel.first_name} {userModel.last_name} + + ); + return ( +
+ {value.map((userModel) => { + let content = ( +
+ {projectData(userModel)} + {userModel.deleted_at !== null && ( + + + + + + {GetTranslation("back.model_archived", { + params: { + model: str( + GetTranslation( + "models.classes.gitlab-projects", + { + choice: 1, + }, + ), + ) + .ucFirst() + .value(), + }, + choice: 1, + })} + + + )} +
+ ); + if (viewAnyUsers && userModel.deleted_at === null) { + content = ( + + + {projectData(userModel)} + + } + tooltipContent={ +

+ {GetTranslation("uwil-backend::back.access_model", { + params: { + model: GetTranslation( + "uwil-backend::models.classes.users", + { + choice: 1, + }, + ), + }, + })} +

+ } + type="button" + size="default" + variant="secondary" + /> + ); + } + return ( +
+ {content} +
+ ); + })} +
+ ); + } + return ( +

+ {GetTranslation("uwil-backend::back.table_not_known")} +

+ ); + }, + }, { accessorKey: "created_at", header: str(