From 88e37bfee8c765db6c733b382f22505b486a8e41 Mon Sep 17 00:00:00 2001 From: Arno Kaimbacher Date: Thu, 30 Oct 2025 14:42:36 +0100 Subject: [PATCH] feat: Enhance Dataset Index with Dynamic Legend and Improved State Management for submitter, editor and reviewer - Added a collapsible legend to display dataset states and available actions. - Implemented localStorage persistence for legend visibility. - Refactored dataset state handling with dynamic classes and labels. - Improved table layout and styling for better user experience. - Updated Tailwind CSS configuration to define new background colors for dataset states. --- .../Http/Submitter/DatasetController.ts | 1 + app/validators/project.ts | 6 +- resources/js/Pages/Admin/Project/Create.vue | 6 +- resources/js/Pages/Admin/Project/Edit.vue | 5 +- resources/js/Pages/Editor/Dataset/Index.vue | 394 +++++++++------- resources/js/Pages/Reviewer/Dataset/Index.vue | 327 ++++++++----- .../js/Pages/Submitter/Dataset/Index.vue | 442 +++++++++++------- tailwind.config.js | 69 ++- 8 files changed, 785 insertions(+), 465 deletions(-) diff --git a/app/Controllers/Http/Submitter/DatasetController.ts b/app/Controllers/Http/Submitter/DatasetController.ts index a308634..cda7da6 100644 --- a/app/Controllers/Http/Submitter/DatasetController.ts +++ b/app/Controllers/Http/Submitter/DatasetController.ts @@ -105,6 +105,7 @@ export default class DatasetController { 'reviewed', 'rejected_editor', 'rejected_reviewer', + 'rejected_to_reviewer', ]) .where('account_id', user.id) .preload('titles') diff --git a/app/validators/project.ts b/app/validators/project.ts index bad6c7c..7f3d1f8 100644 --- a/app/validators/project.ts +++ b/app/validators/project.ts @@ -3,13 +3,13 @@ import vine from '@vinejs/vine'; export const createProjectValidator = vine.compile( vine.object({ - label: vine.string().trim().minLength(1).maxLength(50), + label: vine.string().trim().minLength(1).maxLength(50) .regex(/^[a-z0-9-]+$/), name: vine .string() .trim() .minLength(3) .maxLength(255) - .regex(/^[a-z0-9-]+$/), + .regex(/^[a-zA-Z0-9äöüßÄÖÜ\s-]+$/), description: vine.string().trim().maxLength(255).minLength(5).optional(), }), ); @@ -22,7 +22,7 @@ export const updateProjectValidator = vine.compile( .trim() .minLength(3) .maxLength(255) - .regex(/^[a-z0-9-]+$/), + .regex(/^[a-zA-Z0-9äöüßÄÖÜ\s-]+$/), description: vine.string().trim().maxLength(255).minLength(5).optional(), }), ); diff --git a/resources/js/Pages/Admin/Project/Create.vue b/resources/js/Pages/Admin/Project/Create.vue index 688e0ee..769a041 100644 --- a/resources/js/Pages/Admin/Project/Create.vue +++ b/resources/js/Pages/Admin/Project/Create.vue @@ -39,9 +39,9 @@ const submit = async () => {
- + { {
@@ -60,7 +61,7 @@ const submit = async () => { ({}), }, - // user: { - // type: Object, - // default: () => ({}), - // } }); const flash: ComputedRef = computed(() => { - // let test = usePage(); - // console.log(test); return usePage().props.flash; }); +// Legend visibility state with localStorage persistence +const showLegend = ref(true); + +onMounted(() => { + const savedState = localStorage.getItem('datasetLegendVisible'); + if (savedState !== null) { + showLegend.value = savedState === 'true'; + } +}); + +const toggleLegend = () => { + showLegend.value = !showLegend.value; + localStorage.setItem('datasetLegendVisible', String(showLegend.value)); +}; -// const getRowClass = (dataset) => { -// // (props.options ? 'select' : props.type) -// let rowclass = ''; -// if (dataset.server_state == 'accepted') { -// rowclass = 'bg-accepted'; -// } else if (dataset.server_state == 'rejected_reviewer') { -// rowclass = 'bg-rejected-reviewer'; -// } else if (dataset.server_state == 'reviewed') { -// rowclass = 'bg-reviewed'; -// } else if (dataset.server_state == 'released') { -// rowclass = 'bg-released'; -// } else if (dataset.server_state == 'published') { -// rowclass = 'bg-published'; -// } else { -// rowclass = ''; -// } -// return rowclass; -// }; const getRowClass = (dataset) => { - // (props.options ? 'select' : props.type) - let rowclass = ''; - if (dataset.server_state == 'released') { - rowclass = 'bg-released'; - } else if (dataset.server_state == 'editor_accepted' || dataset.server_state == 'rejected_reviewer') { - rowclass = 'bg-editor-accepted'; - } else if (dataset.server_state == 'reviewed') { - rowclass = 'bg-reviewed'; - } else if (dataset.server_state == 'published') { - rowclass = 'bg-published'; - } else { - rowclass = ''; - } - return rowclass; -}; - -// New method to format server state -const formatServerState = (state: string) => { - if (state === 'inprogress') { - return 'draft'; - } else if (state === 'released') { - return 'submitted'; - } else if (state === 'approved') { - return 'ready for review'; - } else if (state === 'reviewer_accepted') { - return 'in review'; - } - return state; // Return the original state for other cases + // Return Tailwind classes that will be defined in tailwind.config + const stateClasses = { + 'released': 'bg-released dark:bg-released-dark', + 'editor_accepted': 'bg-editor-accepted dark:bg-editor-accepted-dark', + 'rejected_reviewer': 'bg-rejected-reviewer dark:bg-rejected-reviewer-dark', + 'reviewed': 'bg-reviewed dark:bg-reviewed-dark', + 'published': 'bg-published dark:bg-published-dark', + }; + + return stateClasses[dataset.server_state] || ''; }; +// Method to get state badge color +const getStateColor = (state: string) => { + const stateColors = { + 'inprogress': 'bg-sky-200 text-sky-900 dark:bg-sky-900 dark:text-sky-200', + 'released': 'bg-blue-300 text-blue-900 dark:bg-blue-900 dark:text-blue-200', + 'editor_accepted': 'bg-teal-200 text-teal-900 dark:bg-teal-900 dark:text-teal-200', + 'rejected_reviewer': 'bg-amber-200 text-amber-900 dark:bg-amber-900 dark:text-amber-200', + 'rejected_to_reviewer': 'bg-yellow-200 text-yellow-900 dark:bg-yellow-900 dark:text-yellow-200', + 'rejected_editor': 'bg-rose-300 text-rose-900 dark:bg-rose-900 dark:text-rose-200', + 'reviewed': 'bg-yellow-300 text-yellow-900 dark:bg-yellow-900 dark:text-yellow-200', + 'published': 'bg-green-300 text-green-900 dark:bg-green-900 dark:text-green-200', + 'approved': 'bg-cyan-200 text-cyan-900 dark:bg-cyan-900 dark:text-cyan-200', + 'reviewer_accepted': 'bg-lime-200 text-lime-900 dark:bg-lime-900 dark:text-lime-200', + }; + return stateColors[state] || 'bg-sky-200 text-sky-900 dark:bg-sky-900 dark:text-sky-200'; +}; + +// Dynamic legend definitions +const datasetStates = [ + { key: 'released', label: 'Submitted' }, + { key: 'editor_accepted', label: 'In Approval' }, + // { key: 'approved', label: 'Ready for Review' }, + // { key: 'reviewer_accepted', label: 'In Review' }, + { key: 'reviewed', label: 'Reviewed' }, + { key: 'published', label: 'Published' }, + { key: 'rejected_reviewer', label: 'Rejected by Reviewer' }, +]; +const getLabel = (key: string) => { + return datasetStates.find(s => s.key === key)?.label || 'Unknown' +} + +const availableActions = [ + { icon: mdiSquareEditOutline, label: 'Edit', color: 'text-blue-500' }, + { icon: mdiTrayArrowDown, label: 'Receive', color: 'text-cyan-500' }, + { icon: mdiCheckDecagram, label: 'Approve (Send to Reviewer)', color: 'text-teal-600' }, + { icon: mdiAccountArrowLeft, label: 'Reject to Submitter', color: 'text-amber-600' }, + { icon: mdiAccountArrowRight, label: 'Reject to Reviewer', color: 'text-yellow-600' }, + { icon: mdiLibraryShelves, label: 'Classify', color: 'text-blue-500' }, + { icon: mdiPublish, label: 'Publish', color: 'text-green-600' }, + { icon: mdiFingerprint, label: 'Mint DOI', color: 'text-cyan-600' }, +]; + +const truncateTitle = (text: string, length = 50) => { + if (!text) return ''; + return text.length > length ? text.substring(0, length) + '...' : text; +}; - - - \ No newline at end of file + \ No newline at end of file diff --git a/resources/js/Pages/Reviewer/Dataset/Index.vue b/resources/js/Pages/Reviewer/Dataset/Index.vue index 36d2351..e3e0737 100644 --- a/resources/js/Pages/Reviewer/Dataset/Index.vue +++ b/resources/js/Pages/Reviewer/Dataset/Index.vue @@ -1,11 +1,12 @@ \ No newline at end of file diff --git a/resources/js/Pages/Submitter/Dataset/Index.vue b/resources/js/Pages/Submitter/Dataset/Index.vue index 5b01370..0d8ac47 100644 --- a/resources/js/Pages/Submitter/Dataset/Index.vue +++ b/resources/js/Pages/Submitter/Dataset/Index.vue @@ -1,17 +1,18 @@ + \ No newline at end of file diff --git a/tailwind.config.js b/tailwind.config.js index e41b62d..357ffee 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -12,25 +12,66 @@ module.exports = { gray: 'gray', }, 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\")", + 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\")", + }, + backgroundColor: { + // Draft / In Progress - Light blue-gray + 'draft': 'rgb(224 242 254)', // sky-100 + 'draft-dark': 'rgb(12 74 110 / 0.3)', // sky-900/30 + 'inprogress': 'rgb(224 242 254)', // sky-100 + 'inprogress-dark': 'rgb(12 74 110 / 0.3)', // sky-900/30 + + // Released / Submitted - Bright blue + 'released': 'rgb(191 219 254)', // blue-200 + 'released-dark': 'rgb(30 58 138 / 0.3)', // blue-900/30 + + // Editor Accepted - Blue-green (teal) + 'editor-accepted': 'rgb(204 251 241)', // teal-100 + 'editor-accepted-dark': 'rgb(19 78 74 / 0.3)', // teal-900/30 + + // Rejected by Reviewer - Yellow-orange (amber) + 'rejected-reviewer': 'rgb(254 243 199)', // amber-100 + 'rejected-reviewer-dark': 'rgb(120 53 15 / 0.3)', // amber-900/30 + + // Rejected by Editor - Rose/Red (back to submitter) + 'rejected-editor': 'rgb(254 205 211)', // rose-200 + 'rejected-editor-dark': 'rgb(136 19 55 / 0.3)', // rose-900/30 + + // Approved / Ready for Review - Cyan (blue-green) + 'approved': 'rgb(207 250 254)', // cyan-100 + 'approved-dark': 'rgb(22 78 99 / 0.3)', // cyan-900/30 + + // Reviewer Accepted / In Review - Lime yellow-green + 'reviewer-accepted': 'rgb(236 252 203)', // lime-100 + 'reviewer-accepted-dark': 'rgb(54 83 20 / 0.3)', // lime-900/30 + + // Reviewed - Soft yellow + 'reviewed': 'rgb(254 240 138)', // yellow-200 + 'reviewed-dark': 'rgb(113 63 18 / 0.3)', // yellow-900/30 + + // Published - Fresh green + 'published': 'rgb(187 247 208)', // green-200 + 'published-dark': 'rgb(20 83 45 / 0.3)', // green-900/30 }, colors: { - 'primary': '#22C55E', - 'inprogress': 'rgb(94 234 212)', + 'primary': '#22C55E', + 'inprogress': 'rgb(94 234 212)', 'released': 'rgb(52 211 153)', 'editor-accepted': 'rgb(125 211 252)', - 'approved': '#FFEB3B', //A lighter yellow, which is cheerful and can indicate that something is in a pending state. + 'approved': '#FFEB3B', //A lighter yellow, which is cheerful and can indicate that something is in a pending state. 'rejected-editor': '#f97316', 'rejected-reviewer': '#f97316', 'reviewed': '#FFC107', // warm amber, suggesting caution but still positive 'published': '#8BC34A', // lighter green, which is also fresh and positive - 'primary-dark': '#DCFCE7', - 'lime': { + 'primary-dark': '#DCFCE7', + 'lime': { DEFAULT: '#BFCE40', dark: 'rgba(5,46,55,0.7)', - 50: '#FBFCF7', + 50: '#FBFCF7', 100: '#F8FBE1', 200: '#EEF69E', 300: '#DCEC53', @@ -40,7 +81,7 @@ module.exports = { 700: '#357C06', 800: '#295B09', 900: '#20450A', - }, + }, }, fontFamily: { sans: ['Inter', ...defaultTheme.fontFamily.sans], @@ -106,7 +147,7 @@ module.exports = { { values: theme('asideScrollbars') }, ); }), - plugin(function({ addUtilities }) { + plugin(function ({ addUtilities }) { const newUtilities = { '.drag-none': { '-webkit-user-drag': 'none', @@ -115,10 +156,10 @@ module.exports = { '-o-user-drag': 'none', 'user-drag': 'none', }, - } - addUtilities(newUtilities) + }; + addUtilities(newUtilities); }), // As of Tailwind CSS v3.3, the `@tailwindcss/line-clamp` plugin is now included by default // require('@tailwindcss/line-clamp'), ], -}; \ No newline at end of file +};