feat: project colors, timeline filters, sidebar fix, GitLooper agent, and misc improvements

- Fix sidebar double-highlight on /vacations/my (Gitea #6): add isNavItemActive() helper
- Add project color picker (schema + API + modal + timeline rendering)
- Add ProjectCombobox/ResourceCombobox to timeline toolbar
- Show PENDING vacations on timeline with dashed/dimmed style
- Add "show demand projects" preference with localStorage persistence
- Add ProjectAssignmentsTable with total hours/cost columns
- Extend vacation API to accept status arrays
- Add GitLooper formal YAML agent configuration
- Extend user admin with permission overrides UI
- Add delete-assignment use case tests
- Add status-styles.ts shared badge constants
- Centralize formatMoney/formatCents in format.ts

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
2026-03-17 10:22:52 +01:00
parent b0e55786c3
commit eb283147d1
34 changed files with 1545 additions and 255 deletions
@@ -43,6 +43,7 @@ export type TimelineProject = {
clientId?: string | null;
budgetCents?: number;
winProbability?: number;
color?: string | null;
staffingReqs?: unknown;
responsiblePerson?: string | null;
};
@@ -92,6 +93,7 @@ export type ProjectGroup = {
startDate: Date;
endDate: Date;
status: string;
color?: string | null;
resourceRows: { resource: ResourceBrief; allocs: TimelineAssignmentEntry[] }[];
};
@@ -206,9 +208,11 @@ export function TimelineProvider({
// Support URL params: ?eids=EMP-001,EMP-002&projectIds=id1,id2&chapters=ch1
const [filters, setFilters] = useState<TimelineFilters>(() => {
const savedPrefs = readAppPreferences();
const base: TimelineFilters = {
...DEFAULT_FILTERS,
hideCompletedProjects: readAppPreferences().hideCompletedProjects,
hideCompletedProjects: savedPrefs.hideCompletedProjects,
showPlaceholders: savedPrefs.showDemandProjects,
};
const eids = searchParams.get("eids");
if (eids) base.eids = eids.split(",").filter(Boolean);
@@ -304,7 +308,7 @@ export function TimelineProvider({
const demands = entriesView?.demands ?? [];
const { data: vacationEntries = [] } = trpc.vacation.list.useQuery(
{ startDate: viewStart, endDate: viewEnd, status: VacationStatus.APPROVED, limit: 500 },
{ startDate: viewStart, endDate: viewEnd, status: [VacationStatus.APPROVED, VacationStatus.PENDING], limit: 500 },
{ placeholderData: (prev) => prev },
);
@@ -477,6 +481,7 @@ export function TimelineProvider({
startDate: new Date(entry.project.startDate as unknown as string),
endDate: new Date(entry.project.endDate as unknown as string),
status: entry.project.status,
color: (entry.project as { color?: string | null }).color ?? null,
resourceRows: [],
};
projectGroupMap.set(entry.projectId, group);