hotfix(dataset): enhance radio button and checkbox components and add arrayContainsTypes validation
- Added checkbox support to the `FormCheckRadio` component. - Updated the styling of the radio button and checkbox components. - Added the `arrayContainsTypes` validation rule to ensure that arrays contain specific types. - Updated the `dataset` validators and controllers to use the new validation rule. - Updated the `FormCheckRadioGroup` component to correctly handle the `input-value` as a number. - Removed the default value from the `id` column in the `collections` migration. - Added the `array_contains_types` rule to the `adonisrc.ts` file.
This commit is contained in:
parent
9823364670
commit
09f65359f9
8 changed files with 116 additions and 41 deletions
|
@ -35,6 +35,7 @@ export default defineConfig({
|
|||
() => import('#start/rules/dependent_array_min_length'),
|
||||
() => import('#start/rules/referenceValidation'),
|
||||
() => import('#start/rules/valid_mimetype'),
|
||||
() => import('#start/rules/array_contains_types'),
|
||||
],
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
|
@ -207,7 +207,8 @@ export default class DatasetController {
|
|||
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
|
||||
}),
|
||||
)
|
||||
.minLength(1),
|
||||
// .minLength(2)
|
||||
.arrayContainsTypes({ typeA: 'main', typeB: 'translated' }),
|
||||
descriptions: vine
|
||||
.array(
|
||||
vine.object({
|
||||
|
@ -221,7 +222,8 @@ export default class DatasetController {
|
|||
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
|
||||
}),
|
||||
)
|
||||
.minLength(1),
|
||||
// .minLength(1),
|
||||
.arrayContainsTypes({ typeA: 'abstract', typeB: 'translated' }),
|
||||
authors: vine
|
||||
.array(
|
||||
vine.object({
|
||||
|
@ -270,7 +272,6 @@ export default class DatasetController {
|
|||
}
|
||||
return response.redirect().back();
|
||||
}
|
||||
|
||||
public async thirdStep({ request, response }: HttpContext) {
|
||||
const newDatasetSchema = vine.object({
|
||||
// first step
|
||||
|
@ -296,7 +297,8 @@ export default class DatasetController {
|
|||
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
|
||||
}),
|
||||
)
|
||||
.minLength(1),
|
||||
// .minLength(2)
|
||||
.arrayContainsTypes({ typeA: 'main', typeB: 'translated' }),
|
||||
descriptions: vine
|
||||
.array(
|
||||
vine.object({
|
||||
|
@ -310,7 +312,8 @@ export default class DatasetController {
|
|||
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
|
||||
}),
|
||||
)
|
||||
.minLength(1),
|
||||
// .minLength(1),
|
||||
.arrayContainsTypes({ typeA: 'abstract', typeB: 'translated' }),
|
||||
authors: vine
|
||||
.array(
|
||||
vine.object({
|
||||
|
@ -539,11 +542,6 @@ export default class DatasetController {
|
|||
try {
|
||||
await multipart.process();
|
||||
// // Instead of letting an error abort the controller, check if any error occurred
|
||||
// if (fileUploadError) {
|
||||
// // Flash the error and return an inertia view that shows the error message.
|
||||
// session.flash('errors', { 'upload error': [fileUploadError.message] });
|
||||
// return response.redirect().back();
|
||||
// }
|
||||
} catch (error) {
|
||||
// This is where you'd expect to catch any errors.
|
||||
session.flash('errors', error.messages);
|
||||
|
|
|
@ -40,7 +40,8 @@ export const createDatasetValidator = vine.compile(
|
|||
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
|
||||
}),
|
||||
)
|
||||
.minLength(1),
|
||||
// .minLength(2)
|
||||
.arrayContainsTypes({ typeA: 'main', typeB: 'translated' }),
|
||||
descriptions: vine
|
||||
.array(
|
||||
vine.object({
|
||||
|
@ -54,7 +55,8 @@ export const createDatasetValidator = vine.compile(
|
|||
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
|
||||
}),
|
||||
)
|
||||
.minLength(1),
|
||||
// .minLength(1),
|
||||
.arrayContainsTypes({ typeA: 'abstract', typeB: 'translated' }),
|
||||
authors: vine
|
||||
.array(
|
||||
vine.object({
|
||||
|
@ -156,8 +158,7 @@ export const createDatasetValidator = vine.compile(
|
|||
.fileScan({ removeInfected: true }),
|
||||
)
|
||||
.minLength(1),
|
||||
}),
|
||||
);
|
||||
}),);
|
||||
|
||||
/**
|
||||
* Validates the dataset's update action
|
||||
|
@ -187,7 +188,8 @@ export const updateDatasetValidator = vine.compile(
|
|||
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
|
||||
}),
|
||||
)
|
||||
.minLength(1),
|
||||
// .minLength(2)
|
||||
.arrayContainsTypes({ typeA: 'main', typeB: 'translated' }),
|
||||
descriptions: vine
|
||||
.array(
|
||||
vine.object({
|
||||
|
@ -201,7 +203,7 @@ export const updateDatasetValidator = vine.compile(
|
|||
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
|
||||
}),
|
||||
)
|
||||
.minLength(1),
|
||||
.arrayContainsTypes({ typeA: 'abstract', typeB: 'translated' }),
|
||||
authors: vine
|
||||
.array(
|
||||
vine.object({
|
||||
|
|
|
@ -5,7 +5,7 @@ export default class Collections extends BaseSchema {
|
|||
|
||||
public async up() {
|
||||
this.schema.createTable(this.tableName, (table) => {
|
||||
table.increments('id').defaultTo("nextval('collections_id_seq')");
|
||||
table.increments('id');//.defaultTo("nextval('collections_id_seq')");
|
||||
table.integer('role_id').unsigned();
|
||||
table
|
||||
.foreign('role_id', 'collections_role_id_foreign')
|
||||
|
|
|
@ -56,27 +56,15 @@ const isChecked = computed(() => {
|
|||
return Array.isArray(computedValue.value) &&
|
||||
computedValue.value.length > 0 &&
|
||||
computedValue.value[0] === props.inputValue;
|
||||
} else if (props.type === 'checkbox') {
|
||||
return Array.isArray(computedValue.value) &&
|
||||
computedValue.value.length > 0 &&
|
||||
computedValue.value.includes(props.inputValue);
|
||||
}
|
||||
return computedValue.value === props.inputValue;
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<!-- <template>
|
||||
<label v-if="type == 'radio'" :class="[type, 'mr-6 mb-3 last:mr-0 inline-flex items-center cursor-pointer relative']">
|
||||
<input v-model="computedValue" :type="inputType" :name="name" :value="inputValue" class="absolute left-0 opacity-0 -z-1 focus:outline-none focus:ring-0" />
|
||||
<span
|
||||
class="check border-gray-700 border transition-colors duration-200 dark:bg-slate-800 block w-5 h-5 rounded-full"
|
||||
:class="{ 'bg-no-repeat bg-center bg-blue-600 border-blue-600 border-4': isChecked }"/>
|
||||
<span class="pl-2">{{ label }}</span>
|
||||
</label>
|
||||
<label v-else :class="[type, 'mr-6 mb-3 last:mr-0']">
|
||||
<input v-model="computedValue" :type="inputType" :name="name" :value="inputValue" />
|
||||
<span class="check" />
|
||||
<span class="pl-2">{{ label }}</span>
|
||||
</label>
|
||||
</template> -->
|
||||
|
||||
<template>
|
||||
<label v-if="type === 'radio'" :class="[type]"
|
||||
class="mr-6 mb-3 last:mr-0 inline-flex items-center cursor-pointer relative">
|
||||
|
@ -85,14 +73,19 @@ const isChecked = computed(() => {
|
|||
:checked="isChecked" />
|
||||
<span class="check border transition-colors duration-200 dark:bg-slate-800 block w-5 h-5 rounded-full" :class="{
|
||||
'border-gray-700': !isChecked,
|
||||
'bg-radio-checked bg-no-repeat bg-center bg-blue-600 border-blue-600 border-4': isChecked
|
||||
'bg-radio-checked bg-no-repeat bg-center bg-lime-600 border-lime-600 border-4': isChecked
|
||||
}" />
|
||||
<span class="pl-2">{{ label }}</span>
|
||||
<span class="pl-2 control-label">{{ label }}</span>
|
||||
</label>
|
||||
|
||||
<label v-else class="mr-6 mb-3 last:mr-0" :class="[type]">
|
||||
<input v-model="computedValue" :type="inputType" :name="name" :value="inputValue" />
|
||||
<span class="check" />
|
||||
<span class="pl-2">{{ label }}</span>
|
||||
<label v-else-if="type === 'checkbox'" :class="[type]"
|
||||
class="mr-6 mb-3 last:mr-0 inline-flex items-center cursor-pointer relative">
|
||||
<input v-model="computedValue" :type="inputType" :name="name" :value="inputValue"
|
||||
class="absolute left-0 opacity-0 -z-1 focus:outline-none focus:ring-0" />
|
||||
<span class="check border transition-colors duration-200 dark:bg-slate-800 block w-5 h-5 rounded" :class="{
|
||||
'border-gray-700': !isChecked,
|
||||
'bg-checkbox-checked bg-no-repeat bg-center bg-lime-600 border-lime-600 border-4': isChecked
|
||||
}" />
|
||||
<span class="pl-2 control-label">{{ label }}</span>
|
||||
</label>
|
||||
</template>
|
||||
|
|
|
@ -47,7 +47,7 @@ const computedValue = computed({
|
|||
if (props.modelValue.every((item) => typeof item === 'number')) {
|
||||
return props.modelValue;
|
||||
} else if (props.modelValue.every((item) => hasIdAttribute(item))) {
|
||||
const ids = props.modelValue.map((obj) => obj.id.toString());
|
||||
const ids = props.modelValue.map((obj) => obj.id);
|
||||
return ids;
|
||||
}
|
||||
return props.modelValue;
|
||||
|
@ -109,6 +109,6 @@ const inputElClass = computed(() => {
|
|||
</svg>
|
||||
</div>
|
||||
<FormCheckRadio v-for="(value, key) in options" :key="key" v-model="computedValue" :type="type"
|
||||
:name="name" :input-value="key" :label="value" :class="componentClass" />
|
||||
:name="name" :input-value="Number(key)" :label="value" :class="componentClass" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
80
start/rules/array_contains_types.ts
Normal file
80
start/rules/array_contains_types.ts
Normal file
|
@ -0,0 +1,80 @@
|
|||
import { FieldContext } from '@vinejs/vine/types';
|
||||
import vine, { VineArray } from '@vinejs/vine';
|
||||
import { SchemaTypes } from '@vinejs/vine/types';
|
||||
|
||||
type Options = {
|
||||
typeA: string;
|
||||
typeB: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Custom rule to validate an array of titles contains at least one title
|
||||
* with type 'main' and one with type 'translated'.
|
||||
*
|
||||
* This rule expects the validated value to be an array of objects,
|
||||
* where each object has a "type" property.
|
||||
*/
|
||||
async function arrayContainsTypes(value: unknown, options: Options, field: FieldContext) {
|
||||
if (!Array.isArray(value)) {
|
||||
field.report(`The {{field}} must be an array of titles.`, 'array.titlesContainsMainAndTranslated', field);
|
||||
return false;
|
||||
}
|
||||
|
||||
const typeAExpected = options.typeA.toLowerCase();
|
||||
const typeBExpected = options.typeB.toLowerCase();
|
||||
|
||||
// const hasMain = value.some((title: any) => {
|
||||
// return typeof title === 'object' && title !== null && String(title.type).toLowerCase() === 'main';
|
||||
// });
|
||||
|
||||
// const hasTranslated = value.some((title: any) => {
|
||||
// return typeof title === 'object' && title !== null && String(title.type).toLowerCase() === 'translated';
|
||||
// });
|
||||
const hasTypeA = value.some((item: any) => {
|
||||
return typeof item === 'object' && item !== null && String(item.type).toLowerCase() === typeAExpected;
|
||||
});
|
||||
|
||||
const hasTypeB = value.some((item: any) => {
|
||||
return typeof item === 'object' && item !== null && String(item.type).toLowerCase() === typeBExpected;
|
||||
});
|
||||
if (!hasTypeA || !hasTypeB) {
|
||||
let errorMessage = `The ${field.getFieldPath()} array must have at least one '${options.typeA}' item and one '${options.typeB}' item.`;
|
||||
|
||||
// Check for specific field names to produce a more readable message.
|
||||
if (field.getFieldPath() === 'titles') {
|
||||
// For titles we expect one main and minimum one translated title.
|
||||
if (!hasTypeA && !hasTypeB) {
|
||||
errorMessage = 'For titles, define one main title and minimum one translated title.';
|
||||
} else if (!hasTypeA) {
|
||||
errorMessage = 'For titles, define one main title.';
|
||||
} else if (!hasTypeB) {
|
||||
errorMessage = 'For titles, define minimum one translated title.';
|
||||
}
|
||||
} else if (field.getFieldPath() === 'descriptions') {
|
||||
// For descriptions we expect one abstracts description and minimum one translated description.
|
||||
if (!hasTypeA && !hasTypeB) {
|
||||
errorMessage = 'For descriptions, define one abstracts description and minimum one translated description.';
|
||||
} else if (!hasTypeA) {
|
||||
errorMessage = 'For descriptions, define one abstracts description.';
|
||||
} else if (!hasTypeB) {
|
||||
errorMessage = 'For descriptions, define minimum one translated description.';
|
||||
}
|
||||
}
|
||||
|
||||
field.report(errorMessage, 'array.containsTypes', field, options);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export const arrayContainsMainAndTranslatedRule = vine.createRule(arrayContainsTypes);
|
||||
|
||||
declare module '@vinejs/vine' {
|
||||
interface VineArray<Schema extends SchemaTypes> {
|
||||
arrayContainsTypes(options: Options): this;
|
||||
}
|
||||
}
|
||||
|
||||
VineArray.macro('arrayContainsTypes', function <Schema extends SchemaTypes>(this: VineArray<Schema>, options: Options) {
|
||||
return this.use(arrayContainsMainAndTranslatedRule(options));
|
||||
});
|
|
@ -14,6 +14,7 @@ module.exports = {
|
|||
extend: {
|
||||
backgroundImage: {
|
||||
'radio-checked': "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23fff' d='M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z' /%3E%3C/svg%3E\")",
|
||||
'checkbox-checked': "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'%3E%3Cpath style='fill:%23fff' d='M 0.04038059,0.6267767 0.14644661,0.52071068 0.42928932,0.80355339 0.3232233,0.90961941 z M 0.21715729,0.80355339 0.85355339,0.16715729 0.95961941,0.2732233 0.3232233,0.90961941 z'%3E%3C/path%3E%3C/svg%3E\")",
|
||||
},
|
||||
colors: {
|
||||
'primary': '#22C55E',
|
||||
|
|
Loading…
Add table
Reference in a new issue