From 04269ce9cfa0cc7d19feefe73cf40a4b983f3ea2 Mon Sep 17 00:00:00 2001 From: Arno Kaimbacher Date: Thu, 16 Oct 2025 12:04:46 +0200 Subject: [PATCH 1/3] - fix: Update TablePersons component for improved UI and functionality; - refactor file scan options --- resources/js/Components/TablePersons.vue | 312 ++++++++++++++--------- start/rules/file_scan.ts | 4 +- 2 files changed, 196 insertions(+), 120 deletions(-) diff --git a/resources/js/Components/TablePersons.vue b/resources/js/Components/TablePersons.vue index 3dd6d3b..c119d8c 100644 --- a/resources/js/Components/TablePersons.vue +++ b/resources/js/Components/TablePersons.vue @@ -2,8 +2,8 @@ import { computed, ref, watch } from 'vue'; import { mdiTrashCan } from '@mdi/js'; import { mdiDragVariant, mdiChevronLeft, mdiChevronRight } from '@mdi/js'; +import { mdiAccount, mdiDomain } from '@mdi/js'; import BaseIcon from '@/Components/BaseIcon.vue'; -import BaseButtons from '@/Components/BaseButtons.vue'; import BaseButton from '@/Components/BaseButton.vue'; import { Person } from '@/Dataset'; import Draggable from 'vuedraggable'; @@ -21,25 +21,6 @@ interface Props { canReorder?: boolean; } -// const props = defineProps({ -// checkable: Boolean, -// persons: { -// type: Array, -// default: () => [], -// }, -// relation: { -// type: String, -// required: true, -// }, -// contributortypes: { -// type: Object, -// default: () => ({}), -// }, -// errors: { -// type: Object, -// default: () => ({}), -// }, -// }); const props = withDefaults(defineProps(), { checkable: false, persons: () => [], @@ -63,15 +44,18 @@ const perPage = ref(5); const currentPage = ref(0); const dragEnabled = ref(props.canReorder); +// Name type options +const nameTypeOptions = { + 'Personal': 'Personal', + 'Organizational': 'Org' +}; + // Computed properties const items = computed({ get() { return props.persons; }, - // setter set(value) { - // Note: we are using destructuring assignment syntax here. - props.persons.length = 0; props.persons.push(...value); }, @@ -122,19 +106,19 @@ const pagesList = computed(() => { return pages; }); -// const removeAuthor = (key: number) => { -// items.value.splice(key, 1); -// }; // Methods const removeAuthor = (index: number) => { const actualIndex = perPage.value * currentPage.value + index; const person = items.value[actualIndex]; - if (confirm(`Are you sure you want to remove ${person.first_name || ''} ${person.last_name || person.email}?`)) { + const displayName = person.name_type === 'Organizational' + ? person.last_name || person.email + : `${person.first_name || ''} ${person.last_name || person.email}`.trim(); + + if (confirm(`Are you sure you want to remove ${displayName}?`)) { items.value.splice(actualIndex, 1); emit('remove-person', actualIndex, person); - // Adjust current page if needed if (itemsPaginated.value.length === 0 && currentPage.value > 0) { currentPage.value--; } @@ -144,6 +128,12 @@ const removeAuthor = (index: number) => { const updatePerson = (index: number, field: keyof Person, value: any) => { const actualIndex = perPage.value * currentPage.value + index; const person = items.value[actualIndex]; + + // Handle name_type change - clear first_name if switching to Organizational + if (field === 'name_type' && value === 'Organizational') { + person.first_name = ''; + } + (person as any)[field] = value; emit('person-updated', actualIndex, person); }; @@ -170,7 +160,6 @@ const handleDragEnd = (evt: any) => { watch( () => props.persons.length, () => { - // Reset to first page if current page is out of bounds if (currentPage.value >= numPages.value && numPages.value > 0) { currentPage.value = numPages.value - 1; } @@ -189,18 +178,16 @@ const perPageOptions = [ @@ -481,7 +558,6 @@ const perPageOptions = [ @apply bg-white dark:bg-slate-900 rounded-lg shadow-sm; } -/* Improve table responsiveness */ @media (max-width: 768px) { table { font-size: 0.875rem; @@ -492,4 +568,4 @@ const perPageOptions = [ padding: 0.5rem !important; } } - + \ No newline at end of file diff --git a/start/rules/file_scan.ts b/start/rules/file_scan.ts index 8003a9d..71bb8b1 100644 --- a/start/rules/file_scan.ts +++ b/start/rules/file_scan.ts @@ -45,8 +45,8 @@ async function scanFileForViruses(filePath: string | undefined, options: Options active: true, // If true, this module will consider using the clamdscan binary host: options.host, // IP of host to connect to TCP interface, port: options.port, // Port of host to use when connecting to TCP interface - socket: '/var/run/clamav/clamd.socket', // Socket file for connecting via socket - localFallback: false, // Use local clamscan binary if socket/tcp fails + // socket: '/var/run/clamav/clamd.socket', // Socket file for connecting via socket + // localFallback: false, // Use local clamscan binary if socket/tcp fails // port: options.port, multiscan: true, // Scan using all available cores! Yay! }, From f39fe753404836957126f2d425f512c8179f0822 Mon Sep 17 00:00:00 2001 From: Arno Kaimbacher Date: Thu, 16 Oct 2025 15:37:55 +0200 Subject: [PATCH 2/3] feat: Implement project management functionality with CRUD operations and UI integration feat: Implement project management functionality with CRUD operations and UI integration - added projects_controller.ts for crud operations- added views Edit-vue , Index.vue and Create.vue - small adaptions in menu.ts additional routes is start/routes.ts for projects --- app/controllers/projects_controller.ts | 50 ++++++ resources/js/Pages/Admin/Project/Create.vue | 144 ++++++++++++++++ resources/js/Pages/Admin/Project/Edit.vue | 154 +++++++++++++++++ resources/js/Pages/Admin/Project/Index.vue | 182 ++++++++++++++++++++ resources/js/menu.ts | 8 +- start/routes.ts | 14 ++ 6 files changed, 551 insertions(+), 1 deletion(-) create mode 100644 app/controllers/projects_controller.ts create mode 100644 resources/js/Pages/Admin/Project/Create.vue create mode 100644 resources/js/Pages/Admin/Project/Edit.vue create mode 100644 resources/js/Pages/Admin/Project/Index.vue diff --git a/app/controllers/projects_controller.ts b/app/controllers/projects_controller.ts new file mode 100644 index 0000000..7b4d43e --- /dev/null +++ b/app/controllers/projects_controller.ts @@ -0,0 +1,50 @@ +// app/controllers/projects_controller.ts +import Project from '#models/project'; +import type { HttpContext } from '@adonisjs/core/http'; + +export default class ProjectsController { + // GET /settings/projects + public async index({ inertia, auth }: HttpContext) { + const projects = await Project.all(); + // return inertia.render('Admin/Project/Index', { projects }); + return inertia.render('Admin/Project/Index', { + projects: projects, + can: { + edit: await auth.user?.can(['settings']), + create: await auth.user?.can(['settings']), + }, + }); + } + + // GET /settings/projects/create + public async create({ inertia }: HttpContext) { + return inertia.render('Admin/Project/Create'); + } + + // POST /settings/projects + public async store({ request, response, session }: HttpContext) { + const data = request.only(['label', 'name', 'description']); + + await Project.create(data); + + session.flash('success', 'Project created successfully'); + return response.redirect().toRoute('settings.project.index'); + } + + // GET /settings/projects/:id/edit + public async edit({ params, inertia }: HttpContext) { + const project = await Project.findOrFail(params.id); + return inertia.render('Admin/Project/Edit', { project }); + } + + // PUT /settings/projects/:id + public async update({ params, request, response, session }: HttpContext) { + const project = await Project.findOrFail(params.id); + const data = request.only(['label', 'name', 'description']); + + await project.merge(data).save(); + + session.flash('success', 'Project updated successfully'); + return response.redirect().toRoute('settings.project.index'); + } +} diff --git a/resources/js/Pages/Admin/Project/Create.vue b/resources/js/Pages/Admin/Project/Create.vue new file mode 100644 index 0000000..f0f8ee5 --- /dev/null +++ b/resources/js/Pages/Admin/Project/Create.vue @@ -0,0 +1,144 @@ + + + \ No newline at end of file diff --git a/resources/js/Pages/Admin/Project/Edit.vue b/resources/js/Pages/Admin/Project/Edit.vue new file mode 100644 index 0000000..4fceaf1 --- /dev/null +++ b/resources/js/Pages/Admin/Project/Edit.vue @@ -0,0 +1,154 @@ + + + \ No newline at end of file diff --git a/resources/js/Pages/Admin/Project/Index.vue b/resources/js/Pages/Admin/Project/Index.vue new file mode 100644 index 0000000..13dcbf9 --- /dev/null +++ b/resources/js/Pages/Admin/Project/Index.vue @@ -0,0 +1,182 @@ + + + \ No newline at end of file diff --git a/resources/js/menu.ts b/resources/js/menu.ts index 16d08d7..55921f3 100644 --- a/resources/js/menu.ts +++ b/resources/js/menu.ts @@ -12,7 +12,7 @@ import { mdiShieldCrownOutline, mdiLicense, mdiFileDocument, - mdiLibraryShelves + mdiFolderMultiple, } from '@mdi/js'; export default [ @@ -92,6 +92,12 @@ export default [ label: 'Licenses', roles: ['administrator'], }, + { + route: 'settings.project.index', + icon: mdiFolderMultiple, + label: 'Projects', + roles: ['administrator'], + }, ], }, diff --git a/start/routes.ts b/start/routes.ts index fd6d9fd..cd564a4 100644 --- a/start/routes.ts +++ b/start/routes.ts @@ -34,6 +34,7 @@ import DatasetController from '#app/Controllers/Http/Submitter/DatasetController import PersonController from '#app/Controllers/Http/Submitter/PersonController'; import EditorDatasetController from '#app/Controllers/Http/Editor/DatasetController'; import ReviewerDatasetController from '#app/Controllers/Http/Reviewer/DatasetController'; +import ProjectsController from '#app/controllers/projects_controller'; import './routes/api.js'; import { middleware } from './kernel.js'; import db from '@adonisjs/lucid/services/db'; // Import the DB service @@ -234,6 +235,19 @@ router .where('id', router.matchers.number()) .use(middleware.can(['settings'])); + // Project routes + // List all projects + router.get('/projects', [ProjectsController, 'index']).as('project.index'); + // Show create form + router.get('/projects/create', [ProjectsController, 'create']).as('project.create').use(middleware.can(['settings']));; + // Store new project + router.post('/projects', [ProjectsController, 'store']).as('project.store').use(middleware.can(['settings']));; + // Show edit form + router.get('/projects/:id/edit',[ProjectsController, 'edit']).as('project.edit').use(middleware.can(['settings']));; + // Update project + router.put('/projects/:id',[ProjectsController, 'update']).as('project.update').use(middleware.can(['settings']));; + + // Mimetype routes router.get('/mimetype', [MimetypeController, 'index']).as('mimetype.index'); router From 3d8f2354cb2c41e58ddae71570556073d3e21ade Mon Sep 17 00:00:00 2001 From: Arno Kaimbacher Date: Wed, 29 Oct 2025 11:20:27 +0100 Subject: [PATCH 3/3] feat: Enhance Dataset Edit Page with Unsaved Changes Indicator and Improved Structure - Added a progress indicator for unsaved changes at the top of the dataset edit page. - Enhanced the title section with a dataset status badge and improved layout. - Introduced collapsible sections for better organization of form fields. - Improved notifications for success/error messages. - Refactored form fields into distinct sections: Basic Information, Licenses, Titles, Descriptions, Creators & Contributors, Additional Metadata, Geographic Coverage, and Files. - Enhanced loading spinner with a more visually appealing overlay. - Added new project validation logic in the backend with create and update validators. --- app/controllers/projects_controller.ts | 8 +- app/validators/project.ts | 28 + resources/js/Layouts/LayoutAuthenticated.vue | 23 +- resources/js/Pages/Admin/License/Index.vue | 159 +-- resources/js/Pages/Admin/Project/Create.vue | 55 +- resources/js/Pages/Admin/Project/Index.vue | 4 +- resources/js/Pages/Admin/Role/Index.vue | 164 ++-- resources/js/Pages/Dashboard.vue | 138 +-- resources/js/Pages/Submitter/Dataset/Edit.vue | 909 ++++++++++-------- 9 files changed, 863 insertions(+), 625 deletions(-) create mode 100644 app/validators/project.ts diff --git a/app/controllers/projects_controller.ts b/app/controllers/projects_controller.ts index 7b4d43e..21e20df 100644 --- a/app/controllers/projects_controller.ts +++ b/app/controllers/projects_controller.ts @@ -1,6 +1,7 @@ // app/controllers/projects_controller.ts import Project from '#models/project'; import type { HttpContext } from '@adonisjs/core/http'; +import { createProjectValidator, updateProjectValidator } from '#validators/project'; export default class ProjectsController { // GET /settings/projects @@ -23,7 +24,8 @@ export default class ProjectsController { // POST /settings/projects public async store({ request, response, session }: HttpContext) { - const data = request.only(['label', 'name', 'description']); + // Validate the request data + const data = await request.validateUsing(createProjectValidator); await Project.create(data); @@ -40,7 +42,9 @@ export default class ProjectsController { // PUT /settings/projects/:id public async update({ params, request, response, session }: HttpContext) { const project = await Project.findOrFail(params.id); - const data = request.only(['label', 'name', 'description']); + + // Validate the request data + const data = await request.validateUsing(updateProjectValidator); await project.merge(data).save(); diff --git a/app/validators/project.ts b/app/validators/project.ts new file mode 100644 index 0000000..bad6c7c --- /dev/null +++ b/app/validators/project.ts @@ -0,0 +1,28 @@ +// app/validators/project.ts +import vine from '@vinejs/vine'; + +export const createProjectValidator = vine.compile( + vine.object({ + label: vine.string().trim().minLength(1).maxLength(50), + name: vine + .string() + .trim() + .minLength(3) + .maxLength(255) + .regex(/^[a-z0-9-]+$/), + description: vine.string().trim().maxLength(255).minLength(5).optional(), + }), +); + +export const updateProjectValidator = vine.compile( + vine.object({ + // label is NOT included since it's readonly + name: vine + .string() + .trim() + .minLength(3) + .maxLength(255) + .regex(/^[a-z0-9-]+$/), + description: vine.string().trim().maxLength(255).minLength(5).optional(), + }), +); diff --git a/resources/js/Layouts/LayoutAuthenticated.vue b/resources/js/Layouts/LayoutAuthenticated.vue index 15a117c..c077c7b 100644 --- a/resources/js/Layouts/LayoutAuthenticated.vue +++ b/resources/js/Layouts/LayoutAuthenticated.vue @@ -14,11 +14,11 @@ const props = defineProps({ showAsideMenu: { type: Boolean, default: true // Set default value to true + }, + hasProgressBar: { + type: Boolean, + default: false // New prop to indicate if progress bar is shown } - // user: { - // type: Object, - // default: () => ({}), - // } }); @@ -29,9 +29,18 @@ const props = defineProps({ }">
- + 'xl:pl-60': props.showAsideMenu==true, + 'pt-14': !props.hasProgressBar, + 'pt-24': props.hasProgressBar // Increased padding when progress bar is present (pt-14 + height of progress bar) + }" + class="min-h-screen w-screen transition-position lg:w-auto bg-gray-50 dark:bg-slate-800 dark:text-slate-100"> +