feat: ✨ add model select component to the list of dynamic field types
This commit is contained in:
parent
340e53929c
commit
b2a159a84f
7 changed files with 133 additions and 71 deletions
|
|
@ -23,6 +23,7 @@ enum DynamicFieldTypes: int
|
|||
case youtubeid = 12;
|
||||
case date = 13;
|
||||
case color = 14;
|
||||
case model = 15;
|
||||
|
||||
/**
|
||||
* Labels definition.
|
||||
|
|
@ -44,6 +45,7 @@ enum DynamicFieldTypes: int
|
|||
self::youtubeid->name => 'enums.dynamic-field-types.youtubeid',
|
||||
self::date->name => 'enums.dynamic-field-types.date',
|
||||
self::color->name => 'enums.dynamic-field-types.color',
|
||||
self::model->name => 'enums.dynamic-field-types.model',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -64,6 +66,7 @@ enum DynamicFieldTypes: int
|
|||
self::youtubeid->name => 'required|string|size:11',
|
||||
self::date->name => 'required|date',
|
||||
self::color->name => 'required|string|hex_color',
|
||||
self::model->name => 'required|numeric|exists:dynamic_pages,id',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -96,5 +99,6 @@ enum DynamicFieldTypes: int
|
|||
self::youtubeid->name => false,
|
||||
self::date->name => false,
|
||||
self::color->name => false,
|
||||
self::model->name => false,
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,8 +44,6 @@ abstract class UpdateDynamicFieldRequest extends FormRequest
|
|||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh
|
||||
* @phpcs:disable Generic.Metrics.NestingLevel.TooHigh
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
|
|
@ -170,6 +168,14 @@ abstract class UpdateDynamicFieldRequest extends FormRequest
|
|||
$ruleKey => $this->compileValidate(DynamicFieldTypes::color->getValidation(), $input)
|
||||
]);
|
||||
break;
|
||||
case DynamicFieldTypes::model->value:
|
||||
// Set a new rule key for the model name.
|
||||
$ruleKeyReserved = preg_replace('/([^.\s]+)$/', 'reserved_$1', $ruleKey);
|
||||
$rules = array_merge($rules, [
|
||||
$ruleKey => $this->compileValidate(DynamicFieldTypes::model->getValidation(), $input),
|
||||
$ruleKeyReserved => 'required|string|between:3,255',
|
||||
]);
|
||||
break;
|
||||
default:
|
||||
throw new \UnexpectedValueException("Unrecognized dynamic type: " . ($input['type'] ?? 'null'));
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -26,15 +26,16 @@ return [
|
|||
'youtubeid' => 'Youtube video identification',
|
||||
'date' => 'Date',
|
||||
'color' => 'Color',
|
||||
'model' => 'Model',
|
||||
],
|
||||
'contactFormType' => [
|
||||
'collaborator' => [
|
||||
'label' => 'join us as an employee',
|
||||
'slug' => 'join-us-as-an-employee'
|
||||
'slug' => 'join-us-as-an-employee'
|
||||
],
|
||||
'breeder' => [
|
||||
'label' => 'join us as a farmer',
|
||||
'slug' => 'join us as a farmer'
|
||||
'slug' => 'join us as a farmer'
|
||||
],
|
||||
'professional' => [
|
||||
'label' => 'a request as a professional'
|
||||
|
|
@ -44,60 +45,60 @@ return [
|
|||
],
|
||||
'export' => [
|
||||
'label' => 'other countries / Export',
|
||||
'slug' => 'export'
|
||||
'slug' => 'export'
|
||||
],
|
||||
'finance' => [
|
||||
'label' => 'finance',
|
||||
'slug' => 'finance-2' #FIXME: revoir le slug
|
||||
'slug' => 'finance-2' #FIXME: revoir le slug
|
||||
],
|
||||
'engagements' => [
|
||||
'label' => 'our commitments',
|
||||
'slug' => 'our-commitments-2' #FIXME: revoir le slug
|
||||
'slug' => 'our-commitments-2' #FIXME: revoir le slug
|
||||
],
|
||||
'product' => [
|
||||
'label' => 'product information',
|
||||
'slug' => 'product-information'
|
||||
'slug' => 'product-information'
|
||||
],
|
||||
'info' => [
|
||||
'label' => 'other general information',
|
||||
'slug' => 'other-general-information'
|
||||
'slug' => 'other-general-information'
|
||||
],
|
||||
'press' => [
|
||||
'label' => 'a press request'
|
||||
],
|
||||
],
|
||||
'breedType' => [
|
||||
'chicken' => 'Chickens / turkeys / guinea fowl',
|
||||
'duck' => 'Ducks',
|
||||
'chicken' => 'Chickens / turkeys / guinea fowl',
|
||||
'duck' => 'Ducks',
|
||||
'chickenEggs' => 'Laying hens',
|
||||
'other' => 'Other'
|
||||
'other' => 'Other'
|
||||
],
|
||||
'customerAskingType' => [
|
||||
'collectiveRestauration' => 'Mass catering',
|
||||
'commercialRestauration' => 'Commercial catering',
|
||||
'snaking' => 'Snacking',
|
||||
'gastronomy' => 'Gastronomy',
|
||||
'other' => 'Other',
|
||||
'snaking' => 'Snacking',
|
||||
'gastronomy' => 'Gastronomy',
|
||||
'other' => 'Other',
|
||||
],
|
||||
'pressType' => [
|
||||
'pressType' => [
|
||||
'groupLogo' => 'Group logo request',
|
||||
'companyLogo' => 'Subsidiary/company logo request',
|
||||
'brandLogo' => 'Brand logo request',
|
||||
'photo' => 'Photo request',
|
||||
'other' => 'Other'
|
||||
],
|
||||
'news-category' => [
|
||||
'news_rh' => 'HR news',
|
||||
'group_life' => 'Group life',
|
||||
'event' => 'Event',
|
||||
'news-category' => [
|
||||
'news_rh' => 'HR news',
|
||||
'group_life' => 'Group life',
|
||||
'event' => 'Event',
|
||||
'com_financial' => 'Financial Press Releases and Notices',
|
||||
'com' => 'Press release',
|
||||
'news_rse' => 'RSE news',
|
||||
'news_product' => 'Product news',
|
||||
'com' => 'Press release',
|
||||
'news_rse' => 'RSE news',
|
||||
'news_product' => 'Product news',
|
||||
'press_article' => 'Article press',
|
||||
'part_sponso' => 'Partnerships and sponsorships'
|
||||
'part_sponso' => 'Partnerships and sponsorships'
|
||||
],
|
||||
'news-status' => [
|
||||
'news-status' => [
|
||||
'unpublished' => 'Unpublished',
|
||||
'published' => 'Published',
|
||||
],
|
||||
|
|
|
|||
|
|
@ -26,60 +26,61 @@ return [
|
|||
'youtubeid' => 'Identifiant vidéo Youtube',
|
||||
'date' => 'Date',
|
||||
'color' => 'Couleur',
|
||||
'model' => 'Modèle',
|
||||
],
|
||||
'contactFormType' => [
|
||||
'collaborator' => [
|
||||
'label' => 'nous rejoindre en tant que collaborateur',
|
||||
'slug' => 'nous-rejoindre-en-tant-que-collaborateur'
|
||||
'slug' => 'nous-rejoindre-en-tant-que-collaborateur'
|
||||
],
|
||||
'breeder' => [
|
||||
'label' => 'nous rejoindre en tant qu’éleveur',
|
||||
'slug' => 'nous-rejoindre-en-tant-queleveur'
|
||||
'slug' => 'nous-rejoindre-en-tant-queleveur'
|
||||
],
|
||||
'professional' => [
|
||||
'label' => 'une demande en tant que professionnel'
|
||||
],
|
||||
'restauration' => [
|
||||
'label' => 'restaurations, Distributeurs et Détaillants France',
|
||||
'label' => 'restaurations, Distributeurs et Détaillants France',
|
||||
'subtitle' => 'une demande en tant que client / détaillant / distributeur',
|
||||
'slug' => 'une-demande-en-tant-que-client-detaillant-distributeur'
|
||||
'slug' => 'une-demande-en-tant-que-client-detaillant-distributeur'
|
||||
],
|
||||
'export' => [
|
||||
'label' => 'l’export',
|
||||
'slug' => 'lexport'
|
||||
'slug' => 'lexport'
|
||||
],
|
||||
'finance' => [
|
||||
'label' => 'la finance',
|
||||
'slug' => 'la-finance'
|
||||
'slug' => 'la-finance'
|
||||
],
|
||||
'commitments' => [
|
||||
'label' => 'nos engagements',
|
||||
'slug' => 'nos-engagements'
|
||||
'slug' => 'nos-engagements'
|
||||
],
|
||||
'product' => [
|
||||
'label' => 'une information produit',
|
||||
'slug' => 'une-information-produit'
|
||||
'slug' => 'une-information-produit'
|
||||
],
|
||||
'info' => [
|
||||
'label' => 'une autre information générale',
|
||||
'slug' => 'une-autre-information-generale'
|
||||
'slug' => 'une-autre-information-generale'
|
||||
],
|
||||
'press' => [
|
||||
'label' => 'une demande presse'
|
||||
],
|
||||
],
|
||||
'breedType' => [
|
||||
'chicken' => 'Poulets/dindes/pintades',
|
||||
'duck' => 'Canards',
|
||||
'chicken' => 'Poulets/dindes/pintades',
|
||||
'duck' => 'Canards',
|
||||
'chickenEggs' => 'Poules pondeuses',
|
||||
'other' => 'Autre'
|
||||
'other' => 'Autre'
|
||||
],
|
||||
'customerAskingType' => [
|
||||
'collectiveRestauration' => 'Restauration collective',
|
||||
'commercialRestauration' => 'Restauration commerciale',
|
||||
'snaking' => 'Snacking',
|
||||
'gastronomy' => 'Gastronomie',
|
||||
'other' => 'Autres',
|
||||
'snaking' => 'Snacking',
|
||||
'gastronomy' => 'Gastronomie',
|
||||
'other' => 'Autres',
|
||||
],
|
||||
'pressType' => [
|
||||
'groupLogo' => 'Demander le logo du Groupe',
|
||||
|
|
@ -89,17 +90,17 @@ return [
|
|||
'other' => 'Autres'
|
||||
],
|
||||
'news-category' => [
|
||||
'news_rh' => 'Actualités RH',
|
||||
'group_life' => 'Vie du Groupe',
|
||||
'event' => 'Événement',
|
||||
'news_rh' => 'Actualités RH',
|
||||
'group_life' => 'Vie du Groupe',
|
||||
'event' => 'Événement',
|
||||
'com_financial' => 'Communiqués et Avis financiers',
|
||||
'com' => 'Communiqué',
|
||||
'news_rse' => 'Actualités RSE',
|
||||
'news_product' => 'Actualités Produits',
|
||||
'com' => 'Communiqué',
|
||||
'news_rse' => 'Actualités RSE',
|
||||
'news_product' => 'Actualités Produits',
|
||||
'press_article' => 'Article de presse',
|
||||
'part_sponso' => 'Partenariats et sponsorings'
|
||||
'part_sponso' => 'Partenariats et sponsorings'
|
||||
],
|
||||
'news-status' => [
|
||||
'news-status' => [
|
||||
'unpublished' => 'Non publié',
|
||||
'published' => 'Publié',
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
// * 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";
|
||||
import SimpleSwitch from "@/back/Components/Inputs/SimpleSwitch";
|
||||
import SimpleWysiwyg from "@/back/Components/Inputs/SimpleWysiwyg";
|
||||
import SimpleWysiwyg from "@/back/Components/Inputs/Wysiwyg/SimpleWysiwyg";
|
||||
import CompleteInputFile from "@/back/Components/Inputs/CompleteInputFile";
|
||||
import DynamicList from "@/back/Components/DynamicPages/DynamicList";
|
||||
import SimpleTextarea from "@/back/Components/Inputs/SimpleTextarea";
|
||||
|
|
@ -12,17 +13,21 @@ import SimpleDatePicker from "@/back/Components/Inputs/SimpleDatePicker";
|
|||
import SimpleColorPicker from "@/back/Components/Inputs/SimpleColorPicker";
|
||||
import SimpleYoutubeId from "@/back/Components/Inputs/SimpleYoutubeId";
|
||||
import SimpleVimeoId from "@/back/Components/Inputs/SimpleVimeoId";
|
||||
import ModelSelect from "@/back/Components/Inputs/ModelSelect";
|
||||
import {
|
||||
ConfigField,
|
||||
DynamicFieldTypes,
|
||||
DynamicInternalField,
|
||||
LaravelEnum,
|
||||
} from "@/back/types/global";
|
||||
import str from "@/hooks/use-string";
|
||||
import useTrans from "@/hooks/use-translations";
|
||||
import {
|
||||
extractExcludedIdFromExists,
|
||||
extractLaravelValidationValue,
|
||||
extractMinMaxFromValidation,
|
||||
extractRequiredValidation,
|
||||
extractTableFromExists,
|
||||
} from "@/hooks/use-laravel-validation";
|
||||
|
||||
type DynamicInputsProps = {
|
||||
|
|
@ -32,7 +37,7 @@ type DynamicInputsProps = {
|
|||
isChildren?: boolean;
|
||||
setData: (
|
||||
name: string,
|
||||
value: string | boolean | File | object | null,
|
||||
value: string | number | boolean | File | object | null,
|
||||
) => void;
|
||||
reset: (name?: string) => void;
|
||||
parentErrors?: Record<string, string> | null;
|
||||
|
|
@ -49,8 +54,8 @@ export default function DynamicInputs({
|
|||
parentErrors,
|
||||
}: DynamicInputsProps) {
|
||||
// Enums.
|
||||
const enumDynamicFieldTypes = usePage().props
|
||||
.enumDynamicFieldTypes as DynamicFieldTypes;
|
||||
const enumDynamicFieldTypes = usePage().props.enumDynamicFieldTypes as DynamicFieldTypes;
|
||||
const enumElementPosition = usePage().props.enumElementPosition as LaravelEnum;
|
||||
|
||||
// Translations.
|
||||
const transAssociateTo = useTrans("back.form_associate_to");
|
||||
|
|
@ -83,8 +88,7 @@ export default function DynamicInputs({
|
|||
const fieldName = `${name}.${field.key}`;
|
||||
|
||||
// Set the content value from the config key.
|
||||
const fieldValue =
|
||||
content?.[field.key as keyof typeof content] ?? "";
|
||||
const fieldValue = content?.[field.key as keyof typeof content] ?? "";
|
||||
|
||||
switch (field.type) {
|
||||
// String input.
|
||||
|
|
@ -255,18 +259,7 @@ export default function DynamicInputs({
|
|||
<CompleteInputFile
|
||||
key={id}
|
||||
name={fieldName}
|
||||
label={str(
|
||||
useTrans("back.form_associate_to", {
|
||||
params: {
|
||||
attribute: useTrans(
|
||||
"validation.custom.file",
|
||||
),
|
||||
},
|
||||
choice: 1,
|
||||
}),
|
||||
)
|
||||
.ucFirst()
|
||||
.value()}
|
||||
label={getTrans(field.label)}
|
||||
setData={setData}
|
||||
reset={reset}
|
||||
value={fieldValue as string | File | null}
|
||||
|
|
@ -327,9 +320,41 @@ export default function DynamicInputs({
|
|||
setData={setData}
|
||||
value={String(fieldValue)}
|
||||
label={getTrans(field.label)}
|
||||
required={extractRequiredValidation(
|
||||
field.validation,
|
||||
)}
|
||||
required={extractRequiredValidation(field.validation)}
|
||||
/>
|
||||
);
|
||||
// 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 (
|
||||
<ModelSelect
|
||||
name={fieldName}
|
||||
targetModel={targetModel}
|
||||
label={getTrans(field.label)}
|
||||
exclude={extractExcludedIdFromExists(field.validation)}
|
||||
placeholder="ex: John Doe"
|
||||
selected={Number(fieldValue)}
|
||||
required={extractRequiredValidation(field.validation)}
|
||||
setData={setData}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -282,8 +282,8 @@ export default function CompleteInputFile(props: CompleteInputFileProps) {
|
|||
)}
|
||||
<BtnOptionnalLinkWithTooltip
|
||||
className="absolute top-2 right-2"
|
||||
variant={'destructive'}
|
||||
content={<Trash />}
|
||||
variant="destructive"
|
||||
content={<Trash className="w-4 h-4 m-auto" />}
|
||||
onClick={() => {
|
||||
setSelectedFile(null);
|
||||
setData(name, null);
|
||||
|
|
|
|||
|
|
@ -81,3 +81,28 @@ export const extractLaravelValidationValue = (
|
|||
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract table from Laravel exists validation rule.
|
||||
* Example: "required|numeric|exists:dynamic_pages,id"
|
||||
* @param string validation
|
||||
* @return boolean
|
||||
*/
|
||||
export const extractTableFromExists = (rule: string): string | null => {
|
||||
const match = rule.match(/exists:([^,]+)/);
|
||||
if (!match) return null;
|
||||
return match[1].trim() || null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract excluded ID from a Laravel exists validation rule.
|
||||
* Example: "required|numeric|exists:dynamic_pages,id,id,!5"
|
||||
* returns: 5
|
||||
* @param rule string
|
||||
* @return number | null
|
||||
*/
|
||||
export const extractExcludedIdFromExists = (rule: string): number | null => {
|
||||
const match = rule.match(/exists:[^,]+,[^,]+,[^,]+,!([0-9]+)/);
|
||||
if (!match) return null;
|
||||
return Number(match[1]);
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue