hotfix (dataset): enhance dataset editing and validation
- Modified the TableKeywords component to remove the external_key reset when the type is updated, only resetting the value. - Updated the DatasetController to pass authorization checks (`can.edit`, `can.delete`) to the edit view. - Updated the arrayContainsTypes validation rule to improve the error messages for titles and descriptions, clarifying the requirements for main and translated entries. - Updated the Dataset Edit view to: - Remove unused code and comments. - Add authorization checks to the save button. - Add a release button. - Add icons to the save and release buttons. - Add a computed property `hasUnsavedChanges` to determine if there are unsaved changes in the form.
This commit is contained in:
parent
2cb33a779c
commit
c3ae4327b7
4 changed files with 83 additions and 55 deletions
|
@ -926,7 +926,7 @@ export default class DatasetController {
|
||||||
// throw new GeneralException(trans('exceptions.publish.release.update_error'));
|
// throw new GeneralException(trans('exceptions.publish.release.update_error'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async edit({ request, inertia, response }: HttpContext) {
|
public async edit({ request, inertia, response, auth }: HttpContext) {
|
||||||
const id = request.param('id');
|
const id = request.param('id');
|
||||||
const datasetQuery = Dataset.query().where('id', id);
|
const datasetQuery = Dataset.query().where('id', id);
|
||||||
datasetQuery
|
datasetQuery
|
||||||
|
@ -1015,6 +1015,10 @@ export default class DatasetController {
|
||||||
referenceIdentifierTypes: Object.entries(ReferenceIdentifierTypes).map(([key, value]) => ({ value: key, label: value })),
|
referenceIdentifierTypes: Object.entries(ReferenceIdentifierTypes).map(([key, value]) => ({ value: key, label: value })),
|
||||||
relationTypes: Object.entries(RelationTypes).map(([key, value]) => ({ value: key, label: value })),
|
relationTypes: Object.entries(RelationTypes).map(([key, value]) => ({ value: key, label: value })),
|
||||||
doctypes: DatasetTypes,
|
doctypes: DatasetTypes,
|
||||||
|
can: {
|
||||||
|
edit: await auth.user?.can(['dataset-edit']),
|
||||||
|
delete: await auth.user?.can(['dataset-delete']),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -154,7 +154,7 @@ const isKeywordReadOnly = (item: Subject) => {
|
||||||
|
|
||||||
<td data-label="Type" scope="row">
|
<td data-label="Type" scope="row">
|
||||||
<FormControl required v-model="item.type"
|
<FormControl required v-model="item.type"
|
||||||
@update:modelValue="() => { item.external_key = undefined; item.value = ''; }" :type="'select'"
|
@update:modelValue="() => { item.value = ''; }" :type="'select'"
|
||||||
placeholder="[Enter Language]" :options="props.subjectTypes">
|
placeholder="[Enter Language]" :options="props.subjectTypes">
|
||||||
<div class="text-red-400 text-sm" v-if="errors[`subjects.${index}.type`]">
|
<div class="text-red-400 text-sm" v-if="errors[`subjects.${index}.type`]">
|
||||||
{{ errors[`subjects.${index}.type`].join(', ') }}
|
{{ errors[`subjects.${index}.type`].join(', ') }}
|
||||||
|
|
|
@ -468,15 +468,6 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- <div class="mb-4">
|
|
||||||
<label for="description" class="block text-gray-700 font-bold mb-2">Description:</label>
|
|
||||||
<textarea id="description"
|
|
||||||
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
|
|
||||||
v-model="form.type"></textarea>
|
|
||||||
</div> -->
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<!-- <label for="project" class="block text-gray-700 font-bold mb-2">Project:</label>
|
<!-- <label for="project" class="block text-gray-700 font-bold mb-2">Project:</label>
|
||||||
<select
|
<select
|
||||||
|
@ -498,25 +489,15 @@
|
||||||
{{ form.errors['files'].join(', ') }}
|
{{ form.errors['files'].join(', ') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<!-- Add more input fields for the other properties of the dataset -->
|
|
||||||
<!-- <button
|
|
||||||
type="submit"
|
|
||||||
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
|
|
||||||
>
|
|
||||||
Save
|
|
||||||
</button> -->
|
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<BaseButtons>
|
<BaseButtons>
|
||||||
<BaseButton @click.stop="submit" :disabled="form.processing" label="Save" color="info"
|
<BaseButton v-if="can.edit" @click.stop="submit" :disabled="form.processing" label="Save"
|
||||||
:class="{ 'opacity-25': form.processing }" small>
|
color="info" :icon="mdiDisc" :class="{ 'opacity-25': form.processing }" small>
|
||||||
</BaseButton>
|
</BaseButton>
|
||||||
<!-- <button :disabled="form.processing" :class="{ 'opacity-25': form.processing }"
|
<BaseButton v-if="can.edit" :route-name="stardust.route('dataset.release', [dataset.id])"
|
||||||
class="text-base hover:scale-110 focus:outline-none flex justify-center px-4 py-2 rounded font-bold cursor-pointer hover:bg-teal-200 bg-teal-100 text-teal-700 border duration-200 ease-in-out border-teal-600 transition"
|
color="info" :icon="mdiLockOpen" :label="'Release'" small
|
||||||
@click.stop="submit">
|
:disabled="form.processing"
|
||||||
Save
|
:class="{ 'opacity-25': form.processing }" />
|
||||||
</button> -->
|
|
||||||
</BaseButtons>
|
</BaseButtons>
|
||||||
</template>
|
</template>
|
||||||
</CardBox>
|
</CardBox>
|
||||||
|
@ -570,7 +551,9 @@ import {
|
||||||
mdiBookOpenPageVariant,
|
mdiBookOpenPageVariant,
|
||||||
mdiEarthPlus,
|
mdiEarthPlus,
|
||||||
mdiAlertBoxOutline,
|
mdiAlertBoxOutline,
|
||||||
mdiRestore
|
mdiRestore,
|
||||||
|
mdiLockOpen,
|
||||||
|
mdiDisc
|
||||||
} from '@mdi/js';
|
} from '@mdi/js';
|
||||||
import { notify } from '@/notiwind';
|
import { notify } from '@/notiwind';
|
||||||
import NotificationBar from '@/Components/NotificationBar.vue';
|
import NotificationBar from '@/Components/NotificationBar.vue';
|
||||||
|
@ -624,8 +607,10 @@ const props = defineProps({
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => ({}),
|
default: () => ({}),
|
||||||
},
|
},
|
||||||
|
can: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
|
||||||
});
|
});
|
||||||
const flash: ComputedRef<any> = computed(() => {
|
const flash: ComputedRef<any> = computed(() => {
|
||||||
|
@ -650,31 +635,70 @@ const fitBounds: LatLngBoundsExpression = [
|
||||||
];
|
];
|
||||||
const mapId = 'test';
|
const mapId = 'test';
|
||||||
|
|
||||||
// const downloadFile = async (id: string): Promise<string> => {
|
|
||||||
// const response = await axios.get<Blob>(`/api/download/${id}`, {
|
|
||||||
// responseType: 'blob',
|
|
||||||
// });
|
|
||||||
// const url = URL.createObjectURL(response.data);
|
|
||||||
// setTimeout(() => {
|
|
||||||
// URL.revokeObjectURL(url);
|
|
||||||
// }, 1000);
|
|
||||||
// return url;
|
|
||||||
// };
|
|
||||||
|
|
||||||
// for (const file of props.dataset.files) {
|
|
||||||
// // console.log(`${file.name} path is ${file.filePath} here.`);
|
|
||||||
// file.fileSrc = ref("");
|
|
||||||
// // downloadFile(file.id).then((value: string) => {
|
|
||||||
// // file.fileSrc = ref(value);
|
|
||||||
// // form = useForm<Dataset>(props.dataset as Dataset);
|
|
||||||
// // });
|
|
||||||
// }
|
|
||||||
|
|
||||||
props.dataset.filesToDelete = [];
|
props.dataset.filesToDelete = [];
|
||||||
props.dataset.subjectsToDelete = [];
|
props.dataset.subjectsToDelete = [];
|
||||||
props.dataset.referencesToDelete = [];
|
props.dataset.referencesToDelete = [];
|
||||||
let form = useForm<Dataset>(props.dataset as Dataset);
|
let form = useForm<Dataset>(props.dataset as Dataset);
|
||||||
|
|
||||||
|
// Add this computed property to the script section
|
||||||
|
const hasUnsavedChanges = computed(() => {
|
||||||
|
// Check if form is processing
|
||||||
|
if (form.processing) return true;
|
||||||
|
|
||||||
|
// Compare current form state with original dataset
|
||||||
|
// Check basic properties
|
||||||
|
if (form.language !== props.dataset.language) return true;
|
||||||
|
if (form.type !== props.dataset.type) return true;
|
||||||
|
if (form.project_id !== props.dataset.project_id) return true;
|
||||||
|
if (form.embargo_date !== props.dataset.embargo_date) return true;
|
||||||
|
|
||||||
|
// Check if licenses have changed
|
||||||
|
const originalLicenses = Array.isArray(props.dataset.licenses)
|
||||||
|
? props.dataset.licenses.map(l => typeof l === 'object' ? l.id.toString() : l)
|
||||||
|
: [];
|
||||||
|
const currentLicenses = Array.isArray(form.licenses)
|
||||||
|
? form.licenses.map(l => typeof l === 'object' ? l.id.toString() : l)
|
||||||
|
: [];
|
||||||
|
if (JSON.stringify(currentLicenses) !== JSON.stringify(originalLicenses)) return true;
|
||||||
|
|
||||||
|
// Check if titles have changed
|
||||||
|
if (JSON.stringify(form.titles) !== JSON.stringify(props.dataset.titles)) return true;
|
||||||
|
|
||||||
|
// Check if descriptions have changed
|
||||||
|
if (JSON.stringify(form.descriptions) !== JSON.stringify(props.dataset.descriptions)) return true;
|
||||||
|
|
||||||
|
// Check if authors have changed
|
||||||
|
if (JSON.stringify(form.authors) !== JSON.stringify(props.dataset.authors)) return true;
|
||||||
|
|
||||||
|
// Check if contributors have changed
|
||||||
|
if (JSON.stringify(form.contributors) !== JSON.stringify(props.dataset.contributors)) return true;
|
||||||
|
|
||||||
|
// Check if subjects have changed
|
||||||
|
// if (JSON.stringify(form.subjects) !== JSON.stringify(props.dataset.subjects)) return true;
|
||||||
|
let test = JSON.stringify(form.subjects);
|
||||||
|
let test2 = JSON.stringify(props.dataset.subjects);
|
||||||
|
if (test !== test2) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if references have changed
|
||||||
|
if (JSON.stringify(form.references) !== JSON.stringify(props.dataset.references)) return true;
|
||||||
|
|
||||||
|
// Check if coverage has changed
|
||||||
|
if (JSON.stringify(form.coverage) !== JSON.stringify(props.dataset.coverage)) return true;
|
||||||
|
|
||||||
|
// Check if files have changed
|
||||||
|
if (form.files?.length !== props.dataset.files?.length) return true;
|
||||||
|
if (form.filesToDelete?.length > 0) return true;
|
||||||
|
|
||||||
|
// Check if there are new files to upload
|
||||||
|
if (form.files?.some(file => !file.id)) return true;
|
||||||
|
|
||||||
|
// No changes detected
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
const submit = async (): Promise<void> => {
|
const submit = async (): Promise<void> => {
|
||||||
let route = stardust.route('dataset.update', [props.dataset.id]);
|
let route = stardust.route('dataset.update', [props.dataset.id]);
|
||||||
|
|
||||||
|
|
|
@ -44,20 +44,20 @@ async function arrayContainsTypes(value: unknown, options: Options, field: Field
|
||||||
if (field.getFieldPath() === 'titles') {
|
if (field.getFieldPath() === 'titles') {
|
||||||
// For titles we expect one main and minimum one translated title.
|
// For titles we expect one main and minimum one translated title.
|
||||||
if (!hasTypeA && !hasTypeB) {
|
if (!hasTypeA && !hasTypeB) {
|
||||||
errorMessage = 'For titles, define one main title and minimum one translated title.';
|
errorMessage = 'For titles, define at least one main title and at least one Translated title as MAIN TITLE.';
|
||||||
} else if (!hasTypeA) {
|
} else if (!hasTypeA) {
|
||||||
errorMessage = 'For titles, define one main title.';
|
errorMessage = 'For titles, define at least one main title.';
|
||||||
} else if (!hasTypeB) {
|
} else if (!hasTypeB) {
|
||||||
errorMessage = 'For titles, define minimum one translated title.';
|
errorMessage = 'For Titles, define at least one Translated title as MAIN TITLE.';
|
||||||
}
|
}
|
||||||
} else if (field.getFieldPath() === 'descriptions') {
|
} else if (field.getFieldPath() === 'descriptions') {
|
||||||
// For descriptions we expect one abstracts description and minimum one translated description.
|
// For descriptions we expect one abstracts description and minimum one translated description.
|
||||||
if (!hasTypeA && !hasTypeB) {
|
if (!hasTypeA && !hasTypeB) {
|
||||||
errorMessage = 'For descriptions, define one abstract description and minimum one translated description.';
|
errorMessage = 'For descriptions, define at least one abstract and at least one Translated description as MAIN ABSTRACT.';
|
||||||
} else if (!hasTypeA) {
|
} else if (!hasTypeA) {
|
||||||
errorMessage = 'For descriptions, define one abstract description.';
|
errorMessage = 'For descriptions, define at least one abstract.';
|
||||||
} else if (!hasTypeB) {
|
} else if (!hasTypeB) {
|
||||||
errorMessage = 'For descriptions, define minimum one translated description.';
|
errorMessage = 'For Descriptions, define at least one Translated description as MAIN ABSTRACT.';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
editor.link_modal.header
Reference in a new issue