feat(export): Nachweis fuer andere Berater mit deren Projekten
Wer in der Buchung einen Kollegen ausgewaehlt hatte und dann exportierte, bekam im Dialog die eigenen Kunden und Projekte zur Auswahl - fuer einen Nachweis, der jemand anderem gehoert. Der Dialog laedt die Projekte jetzt zu der Person, fuer die der Nachweis entsteht, und laedt neu, wenn man die Person darin wechselt; die alte Kunden- und Projektauswahl wird dabei verworfen, weil sie nicht mehr passt. Bei "Alle Mitarbeiter" stehen alle sichtbaren Projekte zur Wahl. Dafuer nimmt GET /projects ein ?user_id an. Wer fremde Projekte sehen darf, entscheidet dieselbe Sicht wie bei den Zeiten: Mitarbeiter bekommen 403, Vorgesetzte nur ihr Team, Management alle. Die Sammelauswahl heisst fuer Management "Alle Mitarbeiter" statt "Alle meine Mitarbeiter" - sie sieht ohnehin jeden. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ff71cb40e1
commit
dd42fba1cb
@@ -1,9 +1,9 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { Modal } from '../ui/Modal';
|
||||
import { Button, Field, inputClass, Toggle } from '../ui/controls';
|
||||
import { Icon, type IconName } from '../ui/Icon';
|
||||
import { useAuth } from '../../lib/auth';
|
||||
import { download, qs } from '../../lib/api';
|
||||
import { download, projectsApi, qs } from '../../lib/api';
|
||||
import { MONTHS } from '../../lib/format';
|
||||
import { useToast } from '../Toast';
|
||||
import { atLeast, type DirectoryUser, type Project } from '../../types';
|
||||
@@ -33,6 +33,35 @@ export const ExportDialog: React.FC<{
|
||||
const [busy, setBusy] = useState(false);
|
||||
const isLead = atLeast(user, 'lead');
|
||||
|
||||
/*
|
||||
* Kunde und Projekt gehoeren zu der Person, fuer die der Nachweis entsteht -
|
||||
* sonst baut eine Fuehrungskraft den Export ihres Mitarbeiters mit den
|
||||
* eigenen Projekten. Fuer sich selbst reicht die Liste, die die Buchung
|
||||
* ohnehin geladen hat; fuer andere wird sie nachgeladen.
|
||||
*/
|
||||
const [fremdeProjekte, setFremdeProjekte] = useState<Project[] | null>(null);
|
||||
const [laden, setLaden] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (userId === String(user!.id)) {
|
||||
setFremdeProjekte(null);
|
||||
return;
|
||||
}
|
||||
let abgebrochen = false;
|
||||
setLaden(true);
|
||||
// "Alle": die Projekte, die der Fragende ueberhaupt sehen darf.
|
||||
const anfrage = userId === 'all' ? projectsApi.list() : projectsApi.list('mine', Number(userId));
|
||||
anfrage
|
||||
.then((list) => !abgebrochen && setFremdeProjekte(list))
|
||||
.catch(() => !abgebrochen && setFremdeProjekte([]))
|
||||
.finally(() => !abgebrochen && setLaden(false));
|
||||
return () => {
|
||||
abgebrochen = true;
|
||||
};
|
||||
}, [userId, user]);
|
||||
|
||||
const basis = fremdeProjekte ?? projects;
|
||||
|
||||
/*
|
||||
* Zur Auswahl stehen nur laufende Projekte aktiver Kunden. Archivierte
|
||||
* Projekte und stillgelegte Kunden wuerden die Liste mit Aufträgen fuellen,
|
||||
@@ -40,12 +69,18 @@ export const ExportDialog: React.FC<{
|
||||
* nicht verloren: ohne Filter exportiert der Nachweis alles, was die Person
|
||||
* im Monat gebucht hat - und der Projektreport hat einen eigenen Export.
|
||||
*/
|
||||
const waehlbar = useMemo(() => projects.filter((p) => p.is_active && p.customer.is_active !== false), [projects]);
|
||||
const waehlbar = useMemo(() => basis.filter((p) => p.is_active && p.customer.is_active !== false), [basis]);
|
||||
const customers = useMemo(() => {
|
||||
const map = new Map<number, string>();
|
||||
waehlbar.forEach((p) => map.set(p.customer.id, p.customer.name));
|
||||
return [...map.entries()].sort((a, b) => a[1].localeCompare(b[1], 'de'));
|
||||
}, [waehlbar]);
|
||||
|
||||
// Wechselt die Person, passt die alte Auswahl nicht mehr.
|
||||
useEffect(() => {
|
||||
setCustomerId('');
|
||||
setProjectId('');
|
||||
}, [userId]);
|
||||
const projectOptions = waehlbar.filter((p) => !customerId || p.customer_id === customerId);
|
||||
|
||||
// Monate: 18 zurueck, 2 voraus.
|
||||
@@ -137,13 +172,14 @@ export const ExportDialog: React.FC<{
|
||||
{t.is_external ? ' (extern)' : ''}
|
||||
</option>
|
||||
))}
|
||||
<option value="all">Alle meine Mitarbeiter</option>
|
||||
<option value="all">{atLeast(user, 'management') ? 'Alle Mitarbeiter' : 'Alle meine Mitarbeiter'}</option>
|
||||
</select>
|
||||
</Field>
|
||||
)}
|
||||
<Field label="Kunde">
|
||||
<Field label="Kunde" hint={laden ? 'Lädt die Projekte dieser Person…' : undefined}>
|
||||
<select
|
||||
className={inputClass}
|
||||
disabled={laden}
|
||||
value={customerId}
|
||||
onChange={(e) => {
|
||||
setCustomerId(e.target.value ? Number(e.target.value) : '');
|
||||
@@ -159,7 +195,7 @@ export const ExportDialog: React.FC<{
|
||||
</select>
|
||||
</Field>
|
||||
<Field label="Projekt">
|
||||
<select className={inputClass} value={projectId} onChange={(e) => setProjectId(e.target.value ? Number(e.target.value) : '')}>
|
||||
<select className={inputClass} disabled={laden} value={projectId} onChange={(e) => setProjectId(e.target.value ? Number(e.target.value) : '')}>
|
||||
<option value="">Alle Projekte</option>
|
||||
{projectOptions.map((p) => (
|
||||
<option key={p.id} value={p.id}>
|
||||
|
||||
+1
-1
@@ -311,7 +311,7 @@ export type PotInput = Partial<
|
||||
>;
|
||||
|
||||
export const projectsApi = {
|
||||
list: (scope?: 'mine') => get<Project[]>(`/projects${qs({ scope })}`),
|
||||
list: (scope?: 'mine', userId?: number) => get<Project[]>(`/projects${qs({ scope, user_id: userId })}`),
|
||||
get: (id: number) => get<Project>(`/projects/${id}`),
|
||||
create: (data: Record<string, unknown>) => send<Project>('POST', '/projects', data),
|
||||
update: (id: number, data: Record<string, unknown>) => send<Project>('PUT', `/projects/${id}`, data),
|
||||
|
||||
Reference in New Issue
Block a user