From cc45c363b5e46f1267d7337bd546db8f6f42e71a Mon Sep 17 00:00:00 2001 From: Alexis Gousseau Date: Thu, 30 Apr 2026 15:57:29 +0200 Subject: [PATCH] feat: :sparkles: add invoice companies crud #119 --- .../Back/InvoiceCompanyController.php | 245 ++++++++++++++++++ .../Middlewares/HandleInertiaRequests.php | 6 + .../StoreInvoiceCompanyRequest.php | 47 ++++ .../UpdateInvoiceCompanyRequest.php | 21 ++ app/Models/InvoiceCompany.php | 48 ++++ app/Policies/InvoiceCompanyPolicy.php | 112 ++++++++ database/factories/InvoiceCompanyFactory.php | 31 +++ ...2026_04_30_123018_create_notices_table.php | 6 +- ...0_145058_create_invoice_packages_table.php | 6 +- ..._154432_create_invoice_companies_table.php | 34 +++ .../seeders/Classic/InvoiceCompanySeeder.php | 25 ++ lang/fr/models.php | 5 +- .../Layout/Sidebar/Links/InvoiceCompanies.tsx | 73 ++++++ .../ts/back/Pages/InvoiceCompanies/Create.tsx | 63 +++++ .../ts/back/Pages/InvoiceCompanies/Edit.tsx | 71 +++++ .../ts/back/Pages/InvoiceCompanies/Form.tsx | 87 +++++++ .../ts/back/Pages/InvoiceCompanies/Index.tsx | 163 ++++++++++++ .../ts/back/Pages/InvoiceCompanies/Show.tsx | 123 +++++++++ .../ts/back/Pages/InvoicePackages/Form.tsx | 2 - resources/ts/back/Pages/Notices/Form.tsx | 2 - resources/ts/back/types/project.d.ts | 4 + routes/back/invoice-companies.php | 14 + routes/breadcrumbs/invoice-companies.php | 39 +++ 23 files changed, 1215 insertions(+), 12 deletions(-) create mode 100644 app/Http/Controllers/Back/InvoiceCompanyController.php create mode 100644 app/Http/Requests/Back/InvoiceCompanies/StoreInvoiceCompanyRequest.php create mode 100644 app/Http/Requests/Back/InvoiceCompanies/UpdateInvoiceCompanyRequest.php create mode 100644 app/Models/InvoiceCompany.php create mode 100644 app/Policies/InvoiceCompanyPolicy.php create mode 100644 database/factories/InvoiceCompanyFactory.php create mode 100644 database/migrations/2026_04_30_154432_create_invoice_companies_table.php create mode 100644 database/seeders/Classic/InvoiceCompanySeeder.php create mode 100644 resources/ts/back/Components/Layout/Sidebar/Links/InvoiceCompanies.tsx create mode 100644 resources/ts/back/Pages/InvoiceCompanies/Create.tsx create mode 100644 resources/ts/back/Pages/InvoiceCompanies/Edit.tsx create mode 100644 resources/ts/back/Pages/InvoiceCompanies/Form.tsx create mode 100644 resources/ts/back/Pages/InvoiceCompanies/Index.tsx create mode 100644 resources/ts/back/Pages/InvoiceCompanies/Show.tsx create mode 100644 routes/back/invoice-companies.php create mode 100644 routes/breadcrumbs/invoice-companies.php diff --git a/app/Http/Controllers/Back/InvoiceCompanyController.php b/app/Http/Controllers/Back/InvoiceCompanyController.php new file mode 100644 index 0000000..838c5e6 --- /dev/null +++ b/app/Http/Controllers/Back/InvoiceCompanyController.php @@ -0,0 +1,245 @@ +authorizeResource(InvoiceCompany::class); + $this->modelName = InvoiceCompany::modelName(); + $this->modelPage = InvoiceCompany::modelPage(); + } + + /** + * Display a listing of the resource. + * + * @param \Illuminate\Http\Request $request + * @return \Inertia\Response + */ + public function index(Request $request): \Inertia\Response + { + /** @var \Illuminate\Database\Eloquent\Builder<\App\Models\InvoiceCompany> $query */ + $query = InvoiceCompany::query(); + + if ($request->routeIs("back.$this->modelName.trash")) { + $query = $query->onlyTrashed(); + } + + /** @var string $search Search field */ + $search = $request->search; + if ($search) { + $this->searchQuery( + $query, + $search, + null, + // TODO: Fields here. + ); + } + $searchFields = \implode(', ', [ + // TODO: Translations here. + ]); + + /** Sort columns with a query */ + $this->sortQuery($query); + + /** Custom pagination */ + $models = $this->paginate($query); + + return $this->inertiaRender("back/Pages/$this->modelPage/Index", [ + 'models' => CommonResource::collection($models), + 'search' => $search, + 'searchFields' => $searchFields, + ]); + } + + /** + * Show the form for creating a new resource. + * + * @param \App\Models\InvoiceCompany $invoice_company + * @return \Inertia\Response + */ + public function create(InvoiceCompany $invoice_company): \Inertia\Response + { + return $this->inertiaRender("back/Pages/$this->modelPage/Create", [ + 'data' => CommonResource::make($invoice_company), + ]); + } + + /** + * Show a specific resource. + * + * @param \App\Models\InvoiceCompany $invoice_company + * @return \Inertia\Response + */ + public function show(InvoiceCompany $invoice_company): \Inertia\Response + { + return $this->inertiaRender("back/Pages/$this->modelPage/Show", [ + 'data' => CommonResource::make($invoice_company), + ]); + } + + /** + * Store a newly created resource. + * + * @param \App\Http\Requests\Back\InvoiceCompanies\StoreInvoiceCompanyRequest $request + * @return \Illuminate\Http\RedirectResponse + */ + public function store(StoreInvoiceCompanyRequest $request): \Illuminate\Http\RedirectResponse + { + return DB::transaction(function () use ($request) { + $invoice_company = new InvoiceCompany(); + $invoice_company->inertiaFill($request->validated()); + + if ($invoice_company->saveOrFail()) { + return redirect()->route("back.$this->modelName.edit", $invoice_company) + ->with('success', trans('uwil-backend::crud.messages.has_been_created', [ + 'model' => Str::ucfirst(trans_choice("models.classes.$this->modelName", 1)), + ])); + } + return redirect()->back() + ->with('error', trans('uwil-backend::crud.messages.cannot_be_created', [ + 'model' => Str::ucfirst(trans_choice("models.classes.$this->modelName", 1)), + ])); + }); + } + + /** + * Show the form for editing the specified resource. + * + * @param \App\Models\InvoiceCompany $invoice_company + * @return \Inertia\Response + */ + public function edit(InvoiceCompany $invoice_company): \Inertia\Response + { + return $this->inertiaRender("back/Pages/$this->modelPage/Edit", [ + 'data' => CommonResource::make($invoice_company), + ]); + } + + /** + * Update the specified resource. + * + * @param \App\Http\Requests\Back\InvoiceCompanies\UpdateInvoiceCompanyRequest $request + * @param \App\Models\InvoiceCompany $invoice_company + * @return \Illuminate\Http\RedirectResponse + */ + public function update( + UpdateInvoiceCompanyRequest $request, + InvoiceCompany $invoice_company + ): \Illuminate\Http\RedirectResponse { + return DB::transaction(function () use ($request, $invoice_company) { + $invoice_company->inertiaFill($request->validated()); + + if ($invoice_company->saveOrFail()) { + return redirect()->route("back.$this->modelName.edit", $invoice_company) + ->with('success', trans('uwil-backend::crud.messages.has_been_updated', [ + 'model' => Str::ucfirst(trans_choice("models.classes.$this->modelName", 1)), + ])); + } + return redirect()->route("back.$this->modelName.edit", $invoice_company) + ->with('error', trans('uwil-backend::crud.messages.cannot_be_updated', [ + 'model' => Str::ucfirst(trans_choice("models.classes.$this->modelName", 1)), + ])); + }); + } + + /** + * Duplicate the specified resource. + * + * @param \App\Models\InvoiceCompany $invoice_company + * @return \Inertia\Response + */ + public function duplicate(InvoiceCompany $invoice_company): \Inertia\Response + { + return $this->create($invoice_company->replicate()); + } + + /** + * Restore the specified resource. + * + * @param integer $invoice_company + * @return \Illuminate\Http\RedirectResponse + */ + public function restore(int $invoice_company): \Illuminate\Http\RedirectResponse + { + $invoice_company = InvoiceCompany::withTrashed()->findOrFail($invoice_company); + /** @var array */ + $previousQueries = []; + \parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries); + if ($invoice_company->restore()) { + return redirect()->route("back.$this->modelName.trash", $previousQueries) + ->with('success', trans('uwil-backend::crud.messages.has_been_restored', [ + 'model' => Str::ucfirst(trans_choice("models.classes.$this->modelName", 1)), + ])); + } + return redirect()->back() + ->with('error', trans('uwil-backend::crud.messages.cannot_be_restored', [ + 'model' => Str::ucfirst(trans_choice("models.classes.$this->modelName", 1)), + ])); + } + + /** + * Destroy the specified resource. + * + * @param \App\Models\InvoiceCompany $invoice_company + * @return \Illuminate\Http\RedirectResponse + */ + public function destroy(InvoiceCompany $invoice_company): \Illuminate\Http\RedirectResponse + { + /** @var array */ + $previousQueries = []; + \parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries); + if ($invoice_company->delete()) { + return redirect()->route("back.$this->modelName.index", $previousQueries) + ->with('success', trans('uwil-backend::crud.messages.has_been_deleted', [ + 'model' => Str::ucfirst(trans_choice("models.classes.$this->modelName", 1)), + ])); + } + return redirect()->back() + ->with('error', trans('uwil-backend::crud.messages.cannot_be_deleted', [ + 'model' => Str::ucfirst(trans_choice("models.classes.$this->modelName", 1)), + ])); + } + + /** + * Destroy the specified resource. + * + * @param \App\Models\InvoiceCompany|integer $invoice_company + * @return \Illuminate\Http\RedirectResponse + */ + public function forceDestroy(InvoiceCompany|int $invoice_company): \Illuminate\Http\RedirectResponse + { + if (is_int($invoice_company)) { + $invoice_company = InvoiceCompany::withTrashed()->findOrFail($invoice_company); + } + /** @var array */ + $previousQueries = []; + \parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries); + if ($invoice_company->forceDelete()) { + return redirect()->route("back.$this->modelName.trash", $previousQueries) + ->with('success', trans('uwil-backend::crud.messages.has_been_deleted', [ + 'model' => Str::ucfirst(trans_choice("models.classes.$this->modelName", 1)), + ])); + } + return redirect()->back() + ->with('error', trans('uwil-backend::crud.messages.cannot_be_deleted', [ + 'model' => Str::ucfirst(trans_choice("models.classes.$this->modelName", 1)), + ])); + } +} diff --git a/app/Http/Middlewares/HandleInertiaRequests.php b/app/Http/Middlewares/HandleInertiaRequests.php index 1aa680f..12af070 100644 --- a/app/Http/Middlewares/HandleInertiaRequests.php +++ b/app/Http/Middlewares/HandleInertiaRequests.php @@ -3,6 +3,7 @@ namespace App\Http\Middlewares; use App\Enums\NoticeTypeEnum; +use App\Models\InvoiceCompany; use App\Models\InvoicePackage; use App\Models\Notice; use Illuminate\Http\Request; @@ -35,6 +36,11 @@ class HandleInertiaRequests extends Middleware 'viewTrash' => Gate::allows('viewTrash', InvoicePackage::class), 'create' => Gate::allows('create', InvoicePackage::class), ], + 'invoiceCompanies' => [ + 'viewAny' => Gate::allows('viewAny', InvoiceCompany::class), + 'viewTrash' => Gate::allows('viewTrash', InvoiceCompany::class), + 'create' => Gate::allows('create', InvoiceCompany::class), + ], ], ], 'noticeTypeEnum' => NoticeTypeEnum::toArray(), diff --git a/app/Http/Requests/Back/InvoiceCompanies/StoreInvoiceCompanyRequest.php b/app/Http/Requests/Back/InvoiceCompanies/StoreInvoiceCompanyRequest.php new file mode 100644 index 0000000..caeed75 --- /dev/null +++ b/app/Http/Requests/Back/InvoiceCompanies/StoreInvoiceCompanyRequest.php @@ -0,0 +1,47 @@ + + */ + public function rules(): array + { + return [ + 'name' => 'required|string|between:3,255', + ]; + } + + /** + * Get custom attributes for validator errors. + * + * @return array + */ + public function attributes(): array + { + return [ + 'name' => trans('validation.custom.name'), + ]; + } +} diff --git a/app/Http/Requests/Back/InvoiceCompanies/UpdateInvoiceCompanyRequest.php b/app/Http/Requests/Back/InvoiceCompanies/UpdateInvoiceCompanyRequest.php new file mode 100644 index 0000000..af417cd --- /dev/null +++ b/app/Http/Requests/Back/InvoiceCompanies/UpdateInvoiceCompanyRequest.php @@ -0,0 +1,21 @@ +route('invoice_company')); + } +} diff --git a/app/Models/InvoiceCompany.php b/app/Models/InvoiceCompany.php new file mode 100644 index 0000000..114e30b --- /dev/null +++ b/app/Models/InvoiceCompany.php @@ -0,0 +1,48 @@ + + */ + protected $fillable = [ + 'name', + ]; + + /** + * The translatable columns. + * + * @var list + */ + public $translatable = [ + 'name', + ]; +} diff --git a/app/Policies/InvoiceCompanyPolicy.php b/app/Policies/InvoiceCompanyPolicy.php new file mode 100644 index 0000000..edaa1c5 --- /dev/null +++ b/app/Policies/InvoiceCompanyPolicy.php @@ -0,0 +1,112 @@ +value); + } + + /** + * Determine whether the user can view trash models. + * + * @param \Illuminate\Database\Eloquent\Model $authModel + * @return boolean + */ + public function viewTrash(Model $authModel): bool + { + return UserStaticRules::atLeastRole($authModel, UserRoleRegistry::get('developper')->value); + } + + /** + * Determine whether the user can view the model. + * + * @param \Illuminate\Database\Eloquent\Model $authModel + * @return boolean + */ + public function view(Model $authModel): bool + { + return UserStaticRules::atLeastRole($authModel, UserRoleRegistry::get('developper')->value); + } + + /** + * Determine whether the user can create models. + * + * @param \Illuminate\Database\Eloquent\Model $authModel + * @return boolean + */ + public function create(Model $authModel): bool + { + return UserStaticRules::atLeastRole($authModel, UserRoleRegistry::get('developper')->value); + } + + /** + * Determine whether the user can update the model. + * + * @param \Illuminate\Database\Eloquent\Model $authModel + * @return boolean + */ + public function update(Model $authModel): bool + { + return UserStaticRules::atLeastRole($authModel, UserRoleRegistry::get('developper')->value); + } + + /** + * Determine whether the user can duplicate the model. + * + * @param \Illuminate\Database\Eloquent\Model $authModel + * @return boolean + */ + public function duplicate(Model $authModel): bool + { + return UserStaticRules::atLeastRole($authModel, UserRoleRegistry::get('developper')->value); + } + + /** + * Determine whether the user can restore the model. + * + * @param \Illuminate\Database\Eloquent\Model $authModel + * @return boolean + */ + public function restore(Model $authModel): bool + { + return UserStaticRules::atLeastRole($authModel, UserRoleRegistry::get('developper')->value); + } + + /** + * Determine whether the user can soft delete the model. + * + * @param \Illuminate\Database\Eloquent\Model $authModel + * @return boolean + */ + public function delete(Model $authModel): bool + { + return UserStaticRules::atLeastRole($authModel, UserRoleRegistry::get('developper')->value); + } + + /** + * Determine whether the user can permanently delete the model. + * + * @param \Illuminate\Database\Eloquent\Model $authModel + * @return boolean + */ + public function forceDelete(Model $authModel): bool + { + return UserStaticRules::atLeastRole($authModel, UserRoleRegistry::get('developper')->value); + } +} diff --git a/database/factories/InvoiceCompanyFactory.php b/database/factories/InvoiceCompanyFactory.php new file mode 100644 index 0000000..d785edd --- /dev/null +++ b/database/factories/InvoiceCompanyFactory.php @@ -0,0 +1,31 @@ + + */ + protected $model = InvoiceCompany::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + "name" => fake()->word(), + ]; + } +} diff --git a/database/migrations/2026_04_30_123018_create_notices_table.php b/database/migrations/2026_04_30_123018_create_notices_table.php index cc8dd6c..98b2d5a 100644 --- a/database/migrations/2026_04_30_123018_create_notices_table.php +++ b/database/migrations/2026_04_30_123018_create_notices_table.php @@ -18,9 +18,9 @@ return new class extends Migration $table->string('title')->comment('Title of the notice.'); $table->text('message')->comment('Message of the notice.'); $table->unsignedTinyInteger('type')->comment('Type of the notice.'); - $table->timestamp('created_at')->nullable()->comment('Date the notice password was created.'); - $table->timestamp('updated_at')->nullable()->comment('Date the notice password was updated.'); - $table->timestamp('deleted_at')->nullable()->comment('Date the notice password was deleted.'); + $table->timestamp('created_at')->nullable()->comment('Date the notice was created.'); + $table->timestamp('updated_at')->nullable()->comment('Date the notice was updated.'); + $table->timestamp('deleted_at')->nullable()->comment('Date the notice was deleted.'); }); } diff --git a/database/migrations/2026_04_30_145058_create_invoice_packages_table.php b/database/migrations/2026_04_30_145058_create_invoice_packages_table.php index 18f8e36..808de9d 100644 --- a/database/migrations/2026_04_30_145058_create_invoice_packages_table.php +++ b/database/migrations/2026_04_30_145058_create_invoice_packages_table.php @@ -18,9 +18,9 @@ return new class extends Migration $table->string('name')->comment('Name of the invoice package.'); $table->double('amount')->comment('Amount of the invoice package.'); $table->boolean('published')->comment('Published status of the invoice package.'); - $table->timestamp('created_at')->nullable()->comment('Date the invoice package password was created.'); - $table->timestamp('updated_at')->nullable()->comment('Date the invoice package password was updated.'); - $table->timestamp('deleted_at')->nullable()->comment('Date the invoice package password was deleted.'); + $table->timestamp('created_at')->nullable()->comment('Date the invoice package was created.'); + $table->timestamp('updated_at')->nullable()->comment('Date the invoice package was updated.'); + $table->timestamp('deleted_at')->nullable()->comment('Date the invoice package was deleted.'); }); } diff --git a/database/migrations/2026_04_30_154432_create_invoice_companies_table.php b/database/migrations/2026_04_30_154432_create_invoice_companies_table.php new file mode 100644 index 0000000..6c81bb8 --- /dev/null +++ b/database/migrations/2026_04_30_154432_create_invoice_companies_table.php @@ -0,0 +1,34 @@ +id()->comment('Id of the invoice company.'); + $table->string('name')->comment('Name of the invoice company.'); + $table->timestamp('created_at')->nullable()->comment('Date the invoice company was created.'); + $table->timestamp('updated_at')->nullable()->comment('Date the invoice company was updated.'); + $table->timestamp('deleted_at')->nullable()->comment('Date the invoice company was deleted.'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down(): void + { + Schema::dropIfExists('invoice_companies'); + } +}; diff --git a/database/seeders/Classic/InvoiceCompanySeeder.php b/database/seeders/Classic/InvoiceCompanySeeder.php new file mode 100644 index 0000000..cee4ba9 --- /dev/null +++ b/database/seeders/Classic/InvoiceCompanySeeder.php @@ -0,0 +1,25 @@ +create(); + } +} diff --git a/lang/fr/models.php b/lang/fr/models.php index 8f1be0c..8ea3346 100644 --- a/lang/fr/models.php +++ b/lang/fr/models.php @@ -4,7 +4,8 @@ declare(strict_types=1); return [ "classes" => [ - "notices" => "information|informations", - "invoice-packages" => "devis - forfait|devis - forfaits", + "notices" => "information|informations", + "invoice-packages" => "devis - forfait|devis - forfaits", + "invoice-companies" => "devis - société|devis - sociétés", ], ]; diff --git a/resources/ts/back/Components/Layout/Sidebar/Links/InvoiceCompanies.tsx b/resources/ts/back/Components/Layout/Sidebar/Links/InvoiceCompanies.tsx new file mode 100644 index 0000000..cc21ca1 --- /dev/null +++ b/resources/ts/back/Components/Layout/Sidebar/Links/InvoiceCompanies.tsx @@ -0,0 +1,73 @@ +// * Packages +import { Building2Icon } from "lucide-react"; +import { route } from "ziggy-js"; +import { Link } from "@inertiajs/react"; +// * Components shadcn +import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; +import { Button } from "@/components/ui/button"; +// * Other +import { + filterActions, + NavListLinksProps, +} from "@/back/Components/Layout/Sidebar/AppSidebar"; +import { SidebarGroupLink } from "@/back/Components/Layout/Sidebar/SidebarRegistry"; +import { AuthProp } from "@/back/types/global"; +import str from "@/hooks/use-string"; +import { GetTranslation } from "@/lib/utils"; +import useTrans from "@/hooks/use-translations"; +import Views from "@/back/lib/actions"; + +export default function SidebarLinkInvoiceCompanies(auth: AuthProp): SidebarGroupLink { + const modelName = "invoice-companies"; + + // Translations. + const transModel = GetTranslation(`models.classes.${modelName}`, { + choice: 2, + }); + const transCreate = str( + useTrans(`uwil-backend::crud.actions.${Views.CREATION.label}`), + ) + .ucFirst() + .value(); + const transArchives = str(useTrans("uwil-backend::back.archives")) + .ucFirst() + .value(); + + const link: NavListLinksProps = { + title: str(transModel).ucFirst().value(), + model: modelName, + icon: Building2Icon, + show: auth.policies.invoiceCompanies.viewAny, + actions: (model: string) => + filterActions({ + auth, + model, + items: [ + auth.policies.invoiceCompanies.create && ( + + + + ), + auth.policies.invoiceCompanies.viewTrash && ( + + + + ), + ], + }), + }; + + return { link, group: "modules" }; +} diff --git a/resources/ts/back/Pages/InvoiceCompanies/Create.tsx b/resources/ts/back/Pages/InvoiceCompanies/Create.tsx new file mode 100644 index 0000000..02bd839 --- /dev/null +++ b/resources/ts/back/Pages/InvoiceCompanies/Create.tsx @@ -0,0 +1,63 @@ +// * Packages +import { usePage } from "@inertiajs/react"; +import { route } from "ziggy-js"; +import { ArrowLeftIcon } from "lucide-react"; +// * Other +import AuthenticatedLayout from "@/back/Layouts/AuthenticatedLayout"; +import Form from "@/back/Pages/InvoiceCompanies/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"; + +export default function Create({ + data, + modelName, +}: { + data: SingleResource; + modelName: string; +}) { + const { viewAny } = (usePage().props.auth as AuthProp).policies.invoiceCompanies; + const model = data.data as DefaultModel; + + // Translations. + const transBackToList = useTrans("uwil-backend::back.crud_back_to_list"); + const transTitle = useTrans( + route().current(`back.${modelName}.duplicate`) + ? "uwil-backend::crud.helpers.duplication_model" + : "uwil-backend::crud.helpers.creation_model", + { + params: { + model: useTrans(`models.classes.${modelName}`, { + choice: 1, + }), + }, + }, + ); + + const beforeNode = () => { + return ( + viewAny && ( +
+ } + tooltipContent={

{transBackToList}

} + /> +
+ ) + ); + }; + + return ( + +
+ + ); +} diff --git a/resources/ts/back/Pages/InvoiceCompanies/Edit.tsx b/resources/ts/back/Pages/InvoiceCompanies/Edit.tsx new file mode 100644 index 0000000..cf26517 --- /dev/null +++ b/resources/ts/back/Pages/InvoiceCompanies/Edit.tsx @@ -0,0 +1,71 @@ +// * Packages +import { usePage } from "@inertiajs/react"; +import { route } from "ziggy-js"; +import { ArrowLeftIcon } from "lucide-react"; +// * Other +import AuthenticatedLayout from "@/back/Layouts/AuthenticatedLayout"; +import ModelActions from "@/back/Components/ModelActions"; +import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; +import Form from "@/back/Pages/InvoiceCompanies/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"; + +export default function Edit({ + data, + modelName, +}: { + data: SingleResource; + modelName: string; +}) { + const { viewAny } = (usePage().props.auth as AuthProp).policies.invoiceCompanies; + const model = data.data as DefaultModel; + const actionModels = [ + Views.VISUALIZATION, + Views.DUPLICATION, + Views.ARCHIVATION, + ]; + + // Translations. + const transBackToList = useTrans("uwil-backend::back.crud_back_to_list"); + const transTitle = useTrans("uwil-backend::crud.helpers.edition_model", { + params: { + model: useTrans(`models.classes.${modelName}`, { + choice: 1, + }), + }, + }); + + const beforeNode = () => { + return ( + viewAny && ( +
+ } + tooltipContent={

{transBackToList}

} + /> +
+ ) + ); + }; + + return ( + + + + + ); +} diff --git a/resources/ts/back/Pages/InvoiceCompanies/Form.tsx b/resources/ts/back/Pages/InvoiceCompanies/Form.tsx new file mode 100644 index 0000000..372b17c --- /dev/null +++ b/resources/ts/back/Pages/InvoiceCompanies/Form.tsx @@ -0,0 +1,87 @@ +// * Packages +import { useForm, usePage } from "@inertiajs/react"; +import { BookTextIcon } from "lucide-react"; +// * Other +import FieldsetWrapper from "@/back/Components/FieldsetWrapper"; +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 SimpleString from "@/back/Components/Inputs/SimpleString"; +import str from "@/hooks/use-string"; +import { InvoiceCompanyModel } from "@/back/types/project"; +import { AppEnv } from "@/types/global"; + +export default function Form({ + model, + modelName, + modelRoute, + action, + method, +}: { + model?: InvoiceCompanyModel; + modelName: string; + modelRoute: string; + action: string; + method: number; +}) { + const { appEnv } = usePage().props as unknown as { + appEnv: AppEnv; + }; + + /** Form section */ + const { post, setData, data } = useForm({ + name: model?.name ? model?.name[appEnv.locale] : "", + }); + + const submit = useCrudSubmit({ + method, + modelRoute, + modelName, + action, + model, + post, + data, + modelNameTrans: useTrans(`models.classes.${modelName}`, { + choice: 1, + }), + }); + + return ( + + + } + description={useTrans("uwil-backend::back.form_subtitle_global")} + className="sm:grid-cols-2 gap-4" + > + + +
+ +
+ + ); +} diff --git a/resources/ts/back/Pages/InvoiceCompanies/Index.tsx b/resources/ts/back/Pages/InvoiceCompanies/Index.tsx new file mode 100644 index 0000000..4eecf57 --- /dev/null +++ b/resources/ts/back/Pages/InvoiceCompanies/Index.tsx @@ -0,0 +1,163 @@ +// * Packages +import { route } from "ziggy-js"; +import { usePage } from "@inertiajs/react"; +import { Row } from "@tanstack/react-table"; +import { ArchiveIcon, ArrowLeftIcon, PlusIcon } from "lucide-react"; +// * Components shadcn +import { Badge } from "@/components/ui/badge"; +// * Other +import AuthenticatedLayout from "@/back/Layouts/AuthenticatedLayout"; +import DataTable from "@/back/Components/Table/DataTable"; +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 { GetFormatedDate, GetTranslation } from "@/lib/utils"; +import { Views } from "@/back/lib/actions"; +import { AuthProp, DefaultModel, Resource } from "@/back/types/global"; +import str from "@/hooks/use-string"; +import useTrans from "@/hooks/use-translations"; +import RenderTranslationCell from "@/back/Components/Table/RenderTranslationCell"; +import { InvoicePackageModel } from "@/back/types/project"; + +const getColumns = () => { + return [ + { + accessorKey: "name", + header: ({ column }: { column: { id: string } }) => ( + + ), + cell: ({ row }: { row: Row }) => ( + + ), + }, + { + accessorKey: "updated_at", + header: ({ column }: { column: { id: string } }) => ( + + ), + cell: ({ row }: { row: Row }) => ( + + {GetFormatedDate(row.getValue("updated_at"))} + + ), + }, + ]; +}; + +export default function Index({ + models, + searchFields, + modelName, +}: { + models: Resource; + searchFields: string; + modelName: string; +}) { + const { auth } = usePage().props as unknown as { + auth: AuthProp; + }; + const { viewAny, viewTrash, create } = auth.policies.invoiceCompanies; + 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(); + + // Translations. + const transModel = useTrans(`models.classes.${modelName}`, { choice: 2 }); + const transArchiveTitle = useTrans( + "uwil-backend::crud.helpers.archives_model", + { + params: { model: transModel }, + }, + ); + const transTitle = str(isArchiveRoute ? transArchiveTitle : transModel) + .ucFirst() + .value(); + const transCrudBackList = useTrans("uwil-backend::back.crud_back_to_list"); + + const beforeNode = () => { + return ( + isArchiveRoute && + viewAny && ( +
+ } + tooltipContent={

{transCrudBackList}

} + /> +
+ ) + ); + }; + const transCrudAddEntry = useTrans("uwil-backend::back.crud_add_entry"); + const transAccessTo = useTrans("uwil-backend::back.crud_access_to", { + params: { + target: useTrans("uwil-backend::back.archived_entries"), + }, + }); + const afterNode = () => { + return ( + !isArchiveRoute && ( +
+ {viewTrash && ( + } + tooltipContent={

{transAccessTo}

} + variant="outline" + /> + )} + {create && ( + } + tooltipContent={

{transCrudAddEntry}

} + /> + )} +
+ ) + ); + }; + + return ( + + {isArchiveRoute && } + + + + ); +} diff --git a/resources/ts/back/Pages/InvoiceCompanies/Show.tsx b/resources/ts/back/Pages/InvoiceCompanies/Show.tsx new file mode 100644 index 0000000..af026a3 --- /dev/null +++ b/resources/ts/back/Pages/InvoiceCompanies/Show.tsx @@ -0,0 +1,123 @@ +// * Packages +import { usePage } from "@inertiajs/react"; +import { route } from "ziggy-js"; +import { ArrowLeftIcon } 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 BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; +import RenderTranslationCell from "@/back/Components/Table/RenderTranslationCell"; +import { GetFormatedDate, GetTranslation } from "@/lib/utils"; +import { Views } from "@/back/lib/actions"; +import { AuthProp, DefaultModel, SingleResource } from "@/back/types/global"; +import str from "@/hooks/use-string"; +import useTrans from "@/hooks/use-translations"; +import { TranslationField } from "@/back/types/i18n"; + +const getColumns = () => { + return [ + { + accessorKey: "name", + header: str( + GetTranslation("validation.custom.name"), + ) + .ucFirst() + .value(), + cell: ({ value }: { value: TranslationField }) => ( + + ), + }, + { + accessorKey: "created_at", + header: str( + GetTranslation("uwil-backend::validation.attributes.created_at"), + ) + .ucFirst() + .value(), + cell: ({ value }: { value: string }) => ( + {GetFormatedDate(value)} + ), + }, + { + accessorKey: "updated_at", + header: str( + GetTranslation("uwil-backend::validation.attributes.updated_at"), + ) + .ucFirst() + .value(), + cell: ({ value }: { value: string }) => ( + {GetFormatedDate(value)} + ), + }, + { + accessorKey: "deleted_at", + header: str( + GetTranslation("uwil-backend::validation.attributes.deleted_at"), + ) + .ucFirst() + .value(), + cell: ({ value }: { value: string }) => ( + {GetFormatedDate(value)} + ), + }, + ]; +}; + +export default function Show({ + data, + modelName, +}: { + data: SingleResource; + modelName: string; +}) { + const { auth } = usePage().props as unknown as { + auth: AuthProp; + }; + const { viewAny } = auth.policies.invoiceCompanies; + const model = data.data as DefaultModel; + const actionModels = [Views.EDITION, Views.DUPLICATION, Views.ARCHIVATION]; + const transTitle = useTrans( + "uwil-backend::crud.helpers.visualization_model", + { + params: { + model: useTrans(`models.classes.${modelName}`, { + choice: 1, + }), + }, + }, + ); + const columns = getColumns(); + + // Translations. + const transCrudBackToList = useTrans("uwil-backend::back.crud_back_to_list"); + + const beforeNode = () => { + return ( + viewAny && ( +
+ } + tooltipContent={

{transCrudBackToList}

} + /> +
+ ) + ); + }; + + return ( + + + {/* @ts-expect-error Weird error type on cell. */} + + + ); +} diff --git a/resources/ts/back/Pages/InvoicePackages/Form.tsx b/resources/ts/back/Pages/InvoicePackages/Form.tsx index 4a1d7a9..2593cec 100644 --- a/resources/ts/back/Pages/InvoicePackages/Form.tsx +++ b/resources/ts/back/Pages/InvoicePackages/Form.tsx @@ -1,8 +1,6 @@ // * Packages import { useForm, usePage } from "@inertiajs/react"; import { BookTextIcon, EyeIcon } from "lucide-react"; -// * Components shadcn -// // * Other import FieldsetWrapper from "@/back/Components/FieldsetWrapper"; import useTrans from "@/hooks/use-translations"; diff --git a/resources/ts/back/Pages/Notices/Form.tsx b/resources/ts/back/Pages/Notices/Form.tsx index c94e441..e866b53 100644 --- a/resources/ts/back/Pages/Notices/Form.tsx +++ b/resources/ts/back/Pages/Notices/Form.tsx @@ -1,8 +1,6 @@ // * Packages import { useForm, usePage } from "@inertiajs/react"; import { BookTextIcon } from "lucide-react"; -// * Components shadcn -// // * Other import FieldsetWrapper from "@/back/Components/FieldsetWrapper"; import useTrans from "@/hooks/use-translations"; diff --git a/resources/ts/back/types/project.d.ts b/resources/ts/back/types/project.d.ts index c26f79d..fc99f24 100644 --- a/resources/ts/back/types/project.d.ts +++ b/resources/ts/back/types/project.d.ts @@ -12,3 +12,7 @@ type InvoicePackageModel = DefaultModel & { amount: number; published: boolean; }; + +type InvoiceCompanyModel = DefaultModel & { + name: TranslationField; +}; diff --git a/routes/back/invoice-companies.php b/routes/back/invoice-companies.php new file mode 100644 index 0000000..cb04120 --- /dev/null +++ b/routes/back/invoice-companies.php @@ -0,0 +1,14 @@ +name('invoice-companies.trash'); +Route::patch('/invoice-companies/{invoice_company}/restore', [InvoiceCompanyController::class, 'restore']) + ->name('invoice-companies.restore'); +Route::delete('/invoice-companies/{invoice_company}/force', [InvoiceCompanyController::class, 'forceDestroy']) + ->name('invoice-companies.forceDestroy'); +Route::resource('invoice-companies', InvoiceCompanyController::class); +Route::get('/invoice-companies/{invoice_company}/duplicate', [InvoiceCompanyController::class, 'duplicate']) + ->name('invoice-companies.duplicate'); diff --git a/routes/breadcrumbs/invoice-companies.php b/routes/breadcrumbs/invoice-companies.php new file mode 100644 index 0000000..6c7eae5 --- /dev/null +++ b/routes/breadcrumbs/invoice-companies.php @@ -0,0 +1,39 @@ +push( + Str::ucfirst(trans_choice('models.classes.invoice-companies', \INF)), + route('back.invoice-companies.index') + ); +}); +Breadcrumbs::for('back.invoice-companies.create', function (Generator $trail) { + $trail->parent('back.invoice-companies.index'); + $trail->push( + Str::ucfirst(trans_choice('models.classes.invoice-companies', 1)), + route('back.invoice-companies.create') + ); +}); +Breadcrumbs::for('back.invoice-companies.edit', function (Generator $trail) { + $trail->parent('back.invoice-companies.index'); + $trail->push(Str::ucfirst(trans('uwil-backend::crud.actions.edit')), route('back.invoice-companies.edit', [ + 'invoice_company' => request()->invoice_company + ])); +}); +Breadcrumbs::for('back.invoice-companies.duplicate', function (Generator $trail) { + $trail->parent('back.invoice-companies.index'); + $trail->push(Str::ucfirst(trans('uwil-backend::crud.actions.duplicate'))); +}); +Breadcrumbs::for('back.invoice-companies.show', function (Generator $trail) { + $trail->parent('back.invoice-companies.index'); + $trail->push(Str::ucfirst(trans('uwil-backend::crud.actions.visualize')), route('back.invoice-companies.show', [ + 'invoice_company' => request()->invoice_company + ])); +}); +Breadcrumbs::for('back.invoice-companies.trash', function (Generator $trail) { + $trail->parent('back.invoice-companies.index'); + $trail->push(Str::ucfirst(trans('uwil-backend::back.archives'))); +});