UI styling improvements: dashboard headers and navigation

- Restore blue PageHeader on Dashboard (/app-components)
- Update homepage (/) with subtle header design without blue bar
- Add uniform PageHeader styling to application edit page
- Fix Rapporten link on homepage to point to /reports overview
- Improve header descriptions spacing for better readability
This commit is contained in:
2026-01-21 03:24:56 +01:00
parent e276e77fbc
commit cdee0e8819
138 changed files with 24551 additions and 3352 deletions

View File

@@ -5,6 +5,7 @@ import {
getApplicationById,
getConfig,
getRelatedObjects,
refreshApplication,
RelatedObject,
} from '../services/api';
import { StatusBadge, BusinessImportanceBadge } from './ApplicationList';
@@ -133,6 +134,8 @@ export default function ApplicationInfo() {
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [jiraHost, setJiraHost] = useState<string>('');
const [refreshing, setRefreshing] = useState(false);
const [refreshMessage, setRefreshMessage] = useState<string | null>(null);
// Use centralized effort calculation hook
const { calculatedFte, breakdown: effortBreakdown } = useEffortCalculation({
@@ -244,6 +247,44 @@ export default function ApplicationInfo() {
setGovernanceExpanded(prev => !prev);
};
const handleRefresh = async () => {
if (!id || refreshing) return;
setRefreshing(true);
setRefreshMessage(null);
try {
const result = await refreshApplication(id);
setRefreshMessage('Applicatie succesvol gesynchroniseerd vanuit Jira');
// Reload the application data after a short delay to show the success message
setTimeout(async () => {
try {
const refreshedApp = await getApplicationById(id);
setApplication(refreshedApp);
// Clear success message after 3 seconds
setTimeout(() => {
setRefreshMessage(null);
}, 3000);
} catch (err) {
setRefreshMessage('Applicatie gesynchroniseerd, maar herladen mislukt. Ververs de pagina.');
// Clear error message after 5 seconds
setTimeout(() => {
setRefreshMessage(null);
}, 5000);
}
}, 1000);
} catch (err) {
setRefreshMessage(err instanceof Error ? err.message : 'Synchronisatie mislukt');
// Clear error message after 5 seconds
setTimeout(() => {
setRefreshMessage(null);
}, 5000);
} finally {
setRefreshing(false);
}
};
if (loading) {
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50/30 to-slate-50 flex items-center justify-center">
@@ -391,6 +432,24 @@ export default function ApplicationInfo() {
</a>
</Tooltip>
)}
<Tooltip text="Synchroniseer applicatie vanuit Jira">
<button
onClick={handleRefresh}
disabled={refreshing}
className="inline-flex items-center justify-center w-10 h-10 bg-white hover:bg-gray-50 text-blue-600 rounded-lg transition-all shadow-sm hover:shadow-md disabled:opacity-50 disabled:cursor-not-allowed"
>
{refreshing ? (
<svg className="w-5 h-5 animate-spin" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
</svg>
) : (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
)}
</button>
</Tooltip>
</div>
</div>
</div>
@@ -402,6 +461,34 @@ export default function ApplicationInfo() {
</div>
</div>
{/* Refresh message */}
{refreshMessage && (
<div className={`px-6 lg:px-8 py-3 border-b ${
refreshMessage.includes('succesvol') || refreshMessage.includes('gesynchroniseerd')
? 'bg-green-50 border-green-200'
: 'bg-yellow-50 border-yellow-200'
}`}>
<div className="flex items-center gap-2 text-sm">
{refreshMessage.includes('succesvol') || refreshMessage.includes('gesynchroniseerd') ? (
<svg className="w-5 h-5 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
) : (
<svg className="w-5 h-5 text-yellow-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
)}
<span className={
refreshMessage.includes('succesvol') || refreshMessage.includes('gesynchroniseerd')
? 'text-green-800'
: 'text-yellow-800'
}>
{refreshMessage}
</span>
</div>
</div>
)}
{/* Reference warning - only show if reference is truly empty */}
{(() => {
const refValue = application.reference;