+
- {session.ticket_number}
- {formatMinutes(session.rounded_minutes)}
- offen
+ {group.ticketNumber}
+
+ {group.openCount} offen
-
{session.organization_name}
-
{session.activity}
+
{group.organizationName}
+
{formatMinutes(group.totalMinutes)} offen zu bewerten
+
+
+ onNavigate(`/analysis/month/${month}/tickets/${group.ticketId}`)}>
+ Ticket öffnen
+
+
-
onNavigate(`/analysis/month/${month}/tickets/${session.ticket_id}`)}>
- Bewerten
-
-
);
}
-function CrmDayItem({ item, onNavigate }: { item: StatisticsCrmDay; onNavigate: (to: string) => void }) {
+function CrmDayItem({
+ item,
+ acknowledging,
+ onAcknowledge,
+ onNavigate
+}: {
+ item: StatisticsCrmDay;
+ acknowledging: boolean;
+ onAcknowledge: (item: StatisticsCrmDay) => void;
+ onNavigate: (to: string) => void;
+}) {
return (
-
+
+ {
+ if (checked) {
+ onAcknowledge(item);
+ }
+ }}
+ />
{item.ticket_number}
+
{formatDate(`${item.day}T00:00:00`)}
{item.organization_name}
@@ -95,10 +150,15 @@ function CrmDayItem({ item, onNavigate }: { item: StatisticsCrmDay; onNavigate:
{typeof item.delta_minutes === "number" ?
Differenz {formatTeamspaceDelta(item.delta_minutes)} : null}
-
onNavigate(`/analysis/day/${item.day}/tickets/${item.ticket_id}`)}>
- Tag öffnen
-
-
+
+ onAcknowledge(item)}>
+ Zur Kenntnis
+
+ onNavigate(`/analysis/day/${item.day}/tickets/${item.ticket_id}`)}>
+ Tag öffnen
+
+
+
);
}
@@ -109,6 +169,7 @@ export function MonthlyClosePage({ onNavigate }: MonthlyClosePageProps) {
const [loading, setLoading] = useState(false);
const [closing, setClosing] = useState(false);
const [reopening, setReopening] = useState(false);
+ const [acknowledgingKeys, setAcknowledgingKeys] = useState
>(() => new Set());
const loadRequestId = useRef(0);
async function load() {
@@ -181,17 +242,39 @@ export function MonthlyClosePage({ onNavigate }: MonthlyClosePageProps) {
}
}
+ async function acknowledgeMissingCrmDay(item: StatisticsCrmDay) {
+ const key = `${item.ticket_id}:${item.day}`;
+ setAcknowledgingKeys((current) => new Set(current).add(key));
+
+ try {
+ await acknowledgeMissingTicketDayBilling(item.ticket_id, item.day, true);
+ toast.success("Teamspace-Prüfung abgehakt", {
+ description: `${item.ticket_number} am ${formatDate(`${item.day}T00:00:00`)}`
+ });
+ await load();
+ } catch (error) {
+ toast.error("Konnte nicht abgehakt werden", {
+ description: error instanceof Error ? error.message : "Unbekannter Fehler"
+ });
+ } finally {
+ setAcknowledgingKeys((current) => {
+ const next = new Set(current);
+ next.delete(key);
+ return next;
+ });
+ }
+ }
+
const openSessions = stats?.attention.openSessions ?? [];
const missingCrmDays = stats?.attention.missingCrmDays ?? [];
- const crmMismatches = stats?.attention.crmMismatches ?? [];
+ const openTicketGroups = useMemo(() => groupOpenSessions(openSessions), [openSessions]);
const canClose = Boolean(stats && !stats.closed && stats.totals.sessions > 0 && openSessions.length === 0);
const checklist = useMemo(
() => [
- { label: "Offene Bewertungen", count: openSessions.length, blocker: true },
- { label: "Tage ohne Teamspace-Wert", count: missingCrmDays.length, blocker: false },
- { label: "Teamspace-Differenzen", count: crmMismatches.length, blocker: false }
+ { label: "Offene Bewertungen", count: openSessions.length, detail: `${openTicketGroups.length} Ticket(s)`, blocker: true },
+ { label: "Tage ohne Teamspace-Wert", count: missingCrmDays.length, detail: "zur Kenntnisnahme", blocker: false }
],
- [openSessions.length, missingCrmDays.length, crmMismatches.length]
+ [openSessions.length, openTicketGroups.length, missingCrmDays.length]
);
return (
@@ -236,22 +319,13 @@ export function MonthlyClosePage({ onNavigate }: MonthlyClosePageProps) {
Dieser Monat wurde am {formatDateTime(stats.closedAt!)} abgeschlossen.
- ) : openSessions.length > 0 ? (
-
-
- {openSessions.length} offene Bewertung(en) blockieren den Monatsabschluss.
-
- ) : missingCrmDays.length > 0 || crmMismatches.length > 0 ? (
-
-
- Der Monat ist abschließbar, hat aber noch Teamspace-Prüfpunkte.
-
) : null}
-
+
- 0 ? "warn" : "ok"} />
+ 0 ? "warn" : "ok"} />
+ 0 ? "warn" : "ok"} />
@@ -260,14 +334,14 @@ export function MonthlyClosePage({ onNavigate }: MonthlyClosePageProps) {
Checkliste
Bewertungen sind Pflicht. Teamspace-Punkte helfen beim sauberen CRM-Abgleich.
-
+
{checklist.map((item) => (
{item.label}
0 ? "warning" : "success"}>{item.count}
-
{item.blocker ? "Muss erledigt sein." : "Vor Abschluss prüfen."}
+
{item.blocker ? "Muss erledigt sein." : item.detail}
))}
@@ -278,16 +352,16 @@ export function MonthlyClosePage({ onNavigate }: MonthlyClosePageProps) {
Aufräumen
Alles, was für diesen Monat noch Aufmerksamkeit braucht.
-
+
Offene Bewertungen
- {openSessions.map((session) => (
-
+ {openTicketGroups.map((group) => (
+
))}
- {openSessions.length === 0 ? Keine offenen Bewertungen.
: null}
+ {openTicketGroups.length === 0 ? Keine offenen Bewertungen.
: null}
@@ -296,21 +370,16 @@ export function MonthlyClosePage({ onNavigate }: MonthlyClosePageProps) {
Tage ohne Teamspace-Wert
{missingCrmDays.map((item) => (
-
+
))}
{missingCrmDays.length === 0 ? Alle getrackten Tage haben einen Teamspace-Wert.
: null}
-
-
-
-
- Teamspace-Differenzen
-
- {crmMismatches.map((item) => (
-
- ))}
- {crmMismatches.length === 0 ? Keine Abweichungen zwischen Sessions und Teamspace.
: null}
-