feat: timeline multi-select, demand popover, resource hover card, merged tooltips, dark mode fixes

Major timeline enhancements:
- Right-click drag multi-selection with floating action bar (batch delete/assign)
- DemandPopover for demand strip details (replaces broken "Loading" modal)
- ResourceHoverCard on name hover showing skills, rates, role, chapter
- Merged heatmap+vacation tooltips into unified TimelineTooltip component
- Fixed overbooking blink animation (date normalization, z-index ordering)
- Fixed dark mode sticky column bleed-through in project view
- System roles admin page, notification task management, performance review docs

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
2026-03-18 23:43:51 +01:00
parent d0f04f13f8
commit ddec3a927a
67 changed files with 4930 additions and 1166 deletions
@@ -118,6 +118,10 @@ export function SystemSettingsClient() {
const [vacationDefaultDays, setVacationDefaultDays] = useState(28);
const [vacationSaved, setVacationSaved] = useState(false);
// Timeline
const [undoMaxSteps, setUndoMaxSteps] = useState(50);
const [timelineSaved, setTimelineSaved] = useState(false);
const { data: settings, isLoading } = trpc.settings.getSystemSettings.useQuery(undefined, {
staleTime: 0,
});
@@ -152,6 +156,8 @@ export function SystemSettingsClient() {
setAnonymizationSeed("");
// Vacation
setVacationDefaultDays(settings.vacationDefaultDays ?? 28);
// Timeline
setUndoMaxSteps(settings.timelineUndoMaxSteps ?? 50);
}
}, [settings]);
@@ -227,6 +233,13 @@ export function SystemSettingsClient() {
},
});
const saveTimelineMutation = trpc.settings.updateSystemSettings.useMutation({
onSuccess: () => {
setTimelineSaved(true);
setTimeout(() => setTimelineSaved(false), 3000);
},
});
function handleSaveSmtp() {
saveSmtpMutation.mutate({
smtpHost: smtpHost || undefined,
@@ -242,6 +255,10 @@ export function SystemSettingsClient() {
saveVacationMutation.mutate({ vacationDefaultDays });
}
function handleSaveTimeline() {
saveTimelineMutation.mutate({ timelineUndoMaxSteps: undoMaxSteps });
}
function handleSaveAnonymization() {
saveAnonymizationMutation.mutate({
anonymizationEnabled,
@@ -1226,6 +1243,46 @@ export function SystemSettingsClient() {
</div>
</div>
<div className={PANEL_CLASS}>
<div>
<h2 className="text-base font-semibold text-gray-900 dark:text-gray-100 flex items-center">
Timeline <InfoTooltip content="Settings for the timeline view, including undo history depth." />
</h2>
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
Configure timeline behavior and undo/redo history.
</p>
</div>
<div className="max-w-xs">
<label className={LABEL_CLASS}>Undo History Depth</label>
<input
type="number"
className={INPUT_CLASS}
value={undoMaxSteps}
onChange={(e) => setUndoMaxSteps(parseInt(e.target.value, 10) || 50)}
min={1}
max={200}
/>
<p className="text-xs text-gray-400 dark:text-gray-500 mt-1">
Maximum number of undo steps for timeline operations (single moves and batch shifts). Default: 50.
</p>
</div>
<div className="flex items-center gap-3">
<button
type="button"
onClick={handleSaveTimeline}
disabled={saveTimelineMutation.isPending}
className={PRIMARY_BUTTON_CLASS}
>
{saveTimelineMutation.isPending ? "Saving…" : "Save Timeline Settings"}
</button>
{timelineSaved && (
<span className="text-sm text-green-600 dark:text-green-400 font-medium">Saved!</span>
)}
</div>
</div>
<div className={PANEL_CLASS}>
<div>
<h2 className="text-base font-semibold text-gray-900 dark:text-gray-100 flex items-center">