From 201bed73aaed230fa3c4e8baa0d7fbbbb2b87253 Mon Sep 17 00:00:00 2001 From: William Carr Date: Wed, 8 Apr 2026 11:21:47 -0400 Subject: [PATCH 1/5] refactor: remove redundant set-inst visit --- resources/js/Pages/EdaDashboard.jsx | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/resources/js/Pages/EdaDashboard.jsx b/resources/js/Pages/EdaDashboard.jsx index 33a890f..f335d15 100644 --- a/resources/js/Pages/EdaDashboard.jsx +++ b/resources/js/Pages/EdaDashboard.jsx @@ -615,22 +615,13 @@ const raceByPellStatusOptions = data => { }; export default function EdaDashboard({ batch_id: propBatchId }) { - // Get inst_id and set-inst message from Inertia shared props - const { inst_id, set_inst_required_message } = usePage().props; - + const { inst_id } = usePage().props; const [batchInfo, setBatchInfo] = useState(null); const [batchLoading, setBatchLoading] = useState(true); const [edaData, setEdaData] = useState(null); const [loading, setLoading] = useState(false); // Start false, will be set when batch_id is resolved const [error, setError] = useState(null); - useEffect(() => { - if (!inst_id) { - const msg = set_inst_required_message ?? 'Set an institution to proceed.'; - router.visit(`/set-inst?message=${encodeURIComponent(msg)}`); - } - }, [inst_id, set_inst_required_message]); - // Fetch batch info - either from propBatchId or get most recent useEffect(() => { const fetchBatchInfo = async () => { From 271e7a67334a99c3b02864e8f0d950af2e9d243f Mon Sep 17 00:00:00 2001 From: William Carr Date: Wed, 8 Apr 2026 12:26:33 -0400 Subject: [PATCH 2/5] refactor: replace ErrorAlert with more general Alert component --- resources/js/Components/Alert.jsx | 97 ++++++++++++++++++++++++++ resources/js/Components/ErrorAlert.jsx | 67 ------------------ resources/js/Pages/Dashboard.jsx | 7 +- resources/js/Pages/FileUpload.jsx | 11 +-- resources/js/Pages/RunInference.jsx | 4 +- resources/js/Pages/SetInst.jsx | 6 +- 6 files changed, 112 insertions(+), 80 deletions(-) create mode 100644 resources/js/Components/Alert.jsx delete mode 100644 resources/js/Components/ErrorAlert.jsx diff --git a/resources/js/Components/Alert.jsx b/resources/js/Components/Alert.jsx new file mode 100644 index 0000000..3957b41 --- /dev/null +++ b/resources/js/Components/Alert.jsx @@ -0,0 +1,97 @@ +import React from 'react'; +import classNames from 'classnames'; +import { + CheckCircleIcon, + ExclamationTriangleIcon, + InformationCircleIcon, + XCircleIcon, +} from '@heroicons/react/24/solid'; + +const VARIANTS = { + danger: { + Icon: XCircleIcon, + border: 'border-red', + surface: 'bg-red-50', + iconWrap: 'bg-red', + title: 'text-red-900', + item: 'text-red-800', + }, + warning: { + Icon: ExclamationTriangleIcon, + border: 'border-amber-500', + surface: 'bg-amber-50', + iconWrap: 'bg-amber-500', + title: 'text-amber-900', + item: 'text-amber-800', + }, + info: { + Icon: InformationCircleIcon, + border: 'border-sky-500', + surface: 'bg-sky-50', + iconWrap: 'bg-sky-500', + title: 'text-sky-900', + item: 'text-sky-800', + }, + success: { + Icon: CheckCircleIcon, + border: 'border-green-600', + surface: 'bg-green-50', + iconWrap: 'bg-green-600', + title: 'text-green-900', + item: 'text-green-800', + }, +}; + +export default function Alert({ + variant = 'info', + mainMsg, + msgDict, + excludeValue, + className, +}) { + if (mainMsg == undefined || mainMsg == '') { + return null; + } + + const v = VARIANTS[variant] ?? VARIANTS.info; + const Icon = v.Icon; + + return ( +
+
+ +
+
+
+ {mainMsg} +
+
    + {msgDict !== undefined && Object.keys(msgDict).length !== 0 + ? Object.entries(msgDict).map(([k, val]) => + val !== excludeValue ? ( +
  • + {k}: {val} +
  • + ) : null, + ) + : null} +
+
+
+ ); +} diff --git a/resources/js/Components/ErrorAlert.jsx b/resources/js/Components/ErrorAlert.jsx deleted file mode 100644 index eb54410..0000000 --- a/resources/js/Components/ErrorAlert.jsx +++ /dev/null @@ -1,67 +0,0 @@ -import React from 'react'; -import { XCircleIcon } from '@heroicons/react/24/solid'; -import classNames from 'classnames'; - -export default function ErrorAlert({ - mainMsg, - msgDict, - excludeValue, - className, -}) { - if (mainMsg == undefined || mainMsg == '') { - return null; - } - - return ( -
-
- - - - - - - - - - - -
-
-
{mainMsg}
-
    - {msgDict !== undefined && Object.keys(msgDict).length !== 0 ? ( - Object.entries(msgDict).map(([k, v]) => - v !== excludeValue ? ( -
  • - {k}: {v} -
  • - ) : ( - <> - ), - ) - ) : ( - <> - )} -
-
-
- ); -} diff --git a/resources/js/Pages/Dashboard.jsx b/resources/js/Pages/Dashboard.jsx index e387d6c..07ada30 100644 --- a/resources/js/Pages/Dashboard.jsx +++ b/resources/js/Pages/Dashboard.jsx @@ -6,8 +6,8 @@ import Spinner from '@/Components/Spinner'; import AppLayout from '@/Layouts/AppLayout'; import ModelRunHistory from '@/Components/ModelRunHistory'; import HeaderLabel from '@/Components/HeaderLabel'; -import ErrorAlert from '@/Components/ErrorAlert'; import classNames from 'classnames'; +import Alert from '@/Components/Alert'; import { formatModelName } from '@/utils/stringUtils'; import { ChartBarIcon, ArrowUpTrayIcon } from '@heroicons/react/24/outline'; @@ -212,10 +212,11 @@ export default function Dashboard({ modelname }) { ) : error != null && !(error.message == 'NO_MODELS' || error.message == 'NO_RUNS') ? ( - + /> ) : (
- +
); } @@ -130,7 +130,7 @@ export default function FileUpload() { let msg = '[ERROR] Batch creation failed: ' + batchCreationResult; return (
- +
element !== 'ok')) { return (
- + />
- +
); } diff --git a/resources/js/Pages/SetInst.jsx b/resources/js/Pages/SetInst.jsx index 87dd88e..3660ec1 100644 --- a/resources/js/Pages/SetInst.jsx +++ b/resources/js/Pages/SetInst.jsx @@ -3,7 +3,7 @@ import { usePage, router } from '@inertiajs/react'; import AppLayout from '@/Layouts/AppLayout'; import axios from 'axios'; import { Cog8ToothIcon } from '@heroicons/react/24/outline'; -import ErrorAlert from '@/Components/ErrorAlert'; +import Alert from '@/Components/Alert'; import HeaderLabel from '@/Components/HeaderLabel'; export default function SetInstitution() { @@ -84,8 +84,8 @@ export default function SetInstitution() { {message && !hideSetInstError && (
-
)} From 531fbbe1c0af2c99f90c026b8b735bb2bed983e7 Mon Sep 17 00:00:00 2001 From: William Carr Date: Wed, 8 Apr 2026 12:48:54 -0400 Subject: [PATCH 3/5] refactor: remove unused components --- resources/js/Pages/Dashboard.jsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/resources/js/Pages/Dashboard.jsx b/resources/js/Pages/Dashboard.jsx index 07ada30..ab22634 100644 --- a/resources/js/Pages/Dashboard.jsx +++ b/resources/js/Pages/Dashboard.jsx @@ -1,17 +1,11 @@ import axios from 'axios'; import React, { useEffect, useState } from 'react'; import { Chart } from 'react-google-charts'; - import Spinner from '@/Components/Spinner'; import AppLayout from '@/Layouts/AppLayout'; import ModelRunHistory from '@/Components/ModelRunHistory'; -import HeaderLabel from '@/Components/HeaderLabel'; -import classNames from 'classnames'; import Alert from '@/Components/Alert'; import { formatModelName } from '@/utils/stringUtils'; - -import { ChartBarIcon, ArrowUpTrayIcon } from '@heroicons/react/24/outline'; -import Button from '@/Components/Landing/Button'; import PageHeading from '@/Components/PageHeading'; const histogramOptions = { From 234c28c093f2dcf5174ed16ca0a9cdacdf12e458 Mon Sep 17 00:00:00 2001 From: William Carr Date: Wed, 8 Apr 2026 13:18:52 -0400 Subject: [PATCH 4/5] refactor: simplify set institution message --- app/Helpers/InstitutionHelper.php | 4 +- app/Http/Middleware/HandleInertiaRequests.php | 1 - app/Http/Middleware/RequireInstitution.php | 7 +-- resources/js/Pages/SetInst.jsx | 61 +++++++++++++------ routes/web.php | 2 +- 5 files changed, 46 insertions(+), 29 deletions(-) diff --git a/app/Helpers/InstitutionHelper.php b/app/Helpers/InstitutionHelper.php index 38c8526..1f6eca5 100644 --- a/app/Helpers/InstitutionHelper.php +++ b/app/Helpers/InstitutionHelper.php @@ -9,8 +9,6 @@ class InstitutionHelper { - public const SET_INST_REQUIRED_MESSAGE = 'Datakinder must set an institution to proceed.'; - // This calls the API endpoint that checks if the current self user has been allowlisted in any institution's email lists. // Returns the institution id, access_type of the current user if set anywhere. public static function checkSelfInst(Request $request) @@ -70,7 +68,7 @@ public static function GetInstitution(Request $request) return [session()->get('datakinder_inst_id'), '']; } - return ['', self::SET_INST_REQUIRED_MESSAGE]; + return ['', '']; } // Call check self in case the user is set as an allowed user for any institution. diff --git a/app/Http/Middleware/HandleInertiaRequests.php b/app/Http/Middleware/HandleInertiaRequests.php index bd2a455..459b29d 100644 --- a/app/Http/Middleware/HandleInertiaRequests.php +++ b/app/Http/Middleware/HandleInertiaRequests.php @@ -47,7 +47,6 @@ public function share(Request $request): array return $inst ?: null; }, - 'set_inst_required_message' => \App\Helpers\InstitutionHelper::SET_INST_REQUIRED_MESSAGE, ]); } } diff --git a/app/Http/Middleware/RequireInstitution.php b/app/Http/Middleware/RequireInstitution.php index b10d620..8fe2de6 100644 --- a/app/Http/Middleware/RequireInstitution.php +++ b/app/Http/Middleware/RequireInstitution.php @@ -57,18 +57,17 @@ public function handle(Request $request, Closure $next): Response } } - [$inst, $instErr] = InstitutionHelper::GetInstitution($request); + [$inst] = InstitutionHelper::GetInstitution($request); if ($inst !== null && $inst !== '') { $request->attributes->set('inst_id', $inst); return $next($request); } - $message = InstitutionHelper::SET_INST_REQUIRED_MESSAGE; if ($request->expectsJson()) { - return response()->json(['error' => $message], 401); + return response()->json(['error' => 'Institution required.'], 401); } - return redirect()->route('set-inst', ['message' => $message]); + return redirect()->route('set-inst'); } } diff --git a/resources/js/Pages/SetInst.jsx b/resources/js/Pages/SetInst.jsx index 3660ec1..dc005ba 100644 --- a/resources/js/Pages/SetInst.jsx +++ b/resources/js/Pages/SetInst.jsx @@ -7,14 +7,15 @@ import Alert from '@/Components/Alert'; import HeaderLabel from '@/Components/HeaderLabel'; export default function SetInstitution() { - const { inst_id, message } = usePage().props; + const { inst_id } = usePage().props; const [resultList, setResultList] = useState({}); const [error, setError] = useState(null); const [loading, setLoading] = useState(true); - const [currentInstId, setCurrentInstId] = useState(inst_id || ''); const [selectedInstId, setSelectedInstId] = useState(inst_id || ''); - const [hideSetInstError, setHideSetInstError] = useState(false); + const [setInstSuccess, setSetInstSuccess] = useState(null); + const [settingInst, setSettingInst] = useState(false); + const [setInstSubmitError, setSetInstSubmitError] = useState(null); useEffect(() => { // Fetch all institutions @@ -35,30 +36,30 @@ export default function SetInstitution() { const handleSubmit = event => { event.preventDefault(); const inst = selectedInstId; - const resultArea = document.getElementById('result_area'); + setSetInstSuccess(null); if (!inst || inst === '') { - resultArea.innerHTML = - 'Error: Please select an institution'; + setSetInstSubmitError('Please select an institution'); return; } - resultArea.innerHTML = - 'Setting institution...'; - + setSetInstSubmitError(null); + setSettingInst(true); return axios .post('/set-inst-api/' + inst) - .then(res => { + .then(() => { + setSettingInst(false); const institutionName = - Object.entries(resultList).find(([name, id]) => id === inst)?.[0] || + Object.entries(resultList).find(([, id]) => id === inst)?.[0] || 'Unknown'; - setCurrentInstId(inst); // Update current institution after successful change - resultArea.innerHTML = `✓ Successfully set institution to: ${institutionName}`; - setHideSetInstError(true); - router.visit(route('set-inst')); + setSetInstSuccess( + `Successfully set institution to: ${institutionName}`, + ); + router.reload({ only: ['inst_id'] }); }) .catch(e => { - resultArea.innerHTML = `Error: ${e.message || e}`; + setSettingInst(false); + setSetInstSubmitError(e.message || String(e)); }); }; @@ -82,10 +83,11 @@ export default function SetInstitution() { minorTitle="Act as Institution" > - {message && !hideSetInstError && ( + {!inst_id && (
)} @@ -113,7 +115,10 @@ export default function SetInstitution() { style={{ backgroundImage: 'none' }} name="instid" value={selectedInstId} - onChange={e => setSelectedInstId(e.target.value)} + onChange={e => { + setSelectedInstId(e.target.value); + setSetInstSubmitError(null); + }} required disabled={loading} > @@ -149,14 +154,30 @@ export default function SetInstitution() {
-
+ {settingInst && ( +
+ Setting institution... +
+ )} + {setInstSubmitError && ( +
+ + Error: {setInstSubmitError} + +
+ )} + {setInstSuccess && ( +
+ +
+ )}
); diff --git a/routes/web.php b/routes/web.php index a7badec..cae6068 100644 --- a/routes/web.php +++ b/routes/web.php @@ -170,7 +170,7 @@ })->name('create-model'); Route::get('/set-inst', function () { - return Inertia::render('SetInst', ['message' => request('message')]); + return Inertia::render('SetInst'); })->name('set-inst'); Route::get('/add-dk', function () { From 1175dc85ec331166e0cc5461eee85fbda8d0eceb Mon Sep 17 00:00:00 2001 From: William Carr Date: Wed, 8 Apr 2026 13:19:12 -0400 Subject: [PATCH 5/5] chore: format --- resources/js/Pages/Dashboard.jsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/resources/js/Pages/Dashboard.jsx b/resources/js/Pages/Dashboard.jsx index ab22634..d9e0924 100644 --- a/resources/js/Pages/Dashboard.jsx +++ b/resources/js/Pages/Dashboard.jsx @@ -224,7 +224,7 @@ export default function Dashboard({ modelname }) {
{error != null && - (error.message == 'NO_MODELS' || error.message == 'NO_RUNS') ? ( + (error.message == 'NO_MODELS' || error.message == 'NO_RUNS') ? ( <>
@@ -259,7 +259,10 @@ export default function Dashboard({ modelname }) {
)}
- +
) : ( @@ -272,7 +275,7 @@ export default function Dashboard({ modelname }) {
Run Time:
{runDatesToJobDict == undefined || - Object.keys(runDatesToJobDict).length == 0 ? ( + Object.keys(runDatesToJobDict).length == 0 ? (