Implement ST-403 ad hoc activities
This commit is contained in:
parent
57ade6a772
commit
a8366932a0
13 changed files with 491 additions and 51 deletions
|
|
@ -69,6 +69,11 @@ const planningStatusToasts: Record<string, StatusToast> = {
|
|||
title: "Activiteit gepland",
|
||||
message: "Je activiteit staat nu in je dagplanning van vandaag.",
|
||||
},
|
||||
"ad-hoc-activity-saved": {
|
||||
variant: "success",
|
||||
title: "Ongeplande activiteit toegevoegd",
|
||||
message: "Deze activiteit staat nu ook in je daglijst van vandaag.",
|
||||
},
|
||||
"activity-status-saved": {
|
||||
variant: "success",
|
||||
title: "Activiteit bijgewerkt",
|
||||
|
|
@ -88,6 +93,12 @@ const planningErrorToasts: Record<string, StatusToast> = {
|
|||
message:
|
||||
"Controleer naam, categorie, duur, impact en prioriteit en probeer het opnieuw.",
|
||||
},
|
||||
"invalid-ad-hoc-activity-input": {
|
||||
variant: "error",
|
||||
title: "Ongeplande activiteit niet opgeslagen",
|
||||
message:
|
||||
"Controleer naam, categorie, duur en impact en probeer het opnieuw.",
|
||||
},
|
||||
"invalid-activity-status": {
|
||||
variant: "error",
|
||||
title: "Status niet opgeslagen",
|
||||
|
|
@ -109,6 +120,11 @@ const planningErrorToasts: Record<string, StatusToast> = {
|
|||
title: "Evaluatie niet opgeslagen",
|
||||
message: "De extra context bij deze activiteit kon niet worden opgeslagen.",
|
||||
},
|
||||
"ad-hoc-activity-failed": {
|
||||
variant: "error",
|
||||
title: "Ongeplande activiteit niet opgeslagen",
|
||||
message: "De ongeplande activiteit kon niet worden toegevoegd. Probeer het opnieuw.",
|
||||
},
|
||||
};
|
||||
|
||||
export function getDashboardStatusToast(status: string | null): StatusToast | null {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
calculatePlannedPointsTotal,
|
||||
calculateActivityPointsTotal,
|
||||
calculatePlanningMeterSnapshot,
|
||||
deriveActivityEnergyPoints,
|
||||
} from "./meter";
|
||||
|
|
@ -38,7 +38,7 @@ describe("deriveActivityEnergyPoints", () => {
|
|||
describe("calculatePlanningMeterSnapshot", () => {
|
||||
it("somt punten van activiteiten op", () => {
|
||||
expect(
|
||||
calculatePlannedPointsTotal([
|
||||
calculateActivityPointsTotal([
|
||||
{ durationMinutes: 30, impactLevel: "midden", status: "planned" },
|
||||
{ durationMinutes: 90, impactLevel: "laag", status: "planned" },
|
||||
]),
|
||||
|
|
@ -54,7 +54,7 @@ describe("calculatePlanningMeterSnapshot", () => {
|
|||
8,
|
||||
);
|
||||
|
||||
expect(snapshot.plannedPoints).toBe(6);
|
||||
expect(snapshot.totalPoints).toBe(6);
|
||||
expect(snapshot.remainingBudget).toBe(2);
|
||||
expect(snapshot.progressPercent).toBe(75);
|
||||
expect(snapshot.isOverBudget).toBe(false);
|
||||
|
|
@ -66,7 +66,7 @@ describe("calculatePlanningMeterSnapshot", () => {
|
|||
null,
|
||||
);
|
||||
|
||||
expect(snapshot.plannedPoints).toBe(2);
|
||||
expect(snapshot.totalPoints).toBe(2);
|
||||
expect(snapshot.dailyBudget).toBeNull();
|
||||
expect(snapshot.remainingBudget).toBeNull();
|
||||
expect(snapshot.progressPercent).toBeNull();
|
||||
|
|
@ -81,7 +81,7 @@ describe("calculatePlanningMeterSnapshot", () => {
|
|||
6,
|
||||
);
|
||||
|
||||
expect(snapshot.plannedPoints).toBe(9);
|
||||
expect(snapshot.totalPoints).toBe(9);
|
||||
expect(snapshot.remainingBudget).toBe(-3);
|
||||
expect(snapshot.isOverBudget).toBe(true);
|
||||
expect(snapshot.progressPercent).toBe(100);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import type {
|
|||
} from "@/lib/planning/types";
|
||||
|
||||
export type PlanningMeterSnapshot = {
|
||||
plannedPoints: number;
|
||||
totalPoints: number;
|
||||
activityCount: number;
|
||||
dailyBudget: number | null;
|
||||
remainingBudget: number | null;
|
||||
|
|
@ -59,7 +59,7 @@ export function deriveActivityEnergyPoints(input: ActivityMeterInput): number {
|
|||
);
|
||||
}
|
||||
|
||||
export function calculatePlannedPointsTotal(
|
||||
export function calculateActivityPointsTotal(
|
||||
activities: Pick<ActivityRecord, "durationMinutes" | "impactLevel" | "status">[],
|
||||
): number {
|
||||
return activities.reduce(
|
||||
|
|
@ -72,11 +72,11 @@ export function calculatePlanningMeterSnapshot(
|
|||
activities: Pick<ActivityRecord, "durationMinutes" | "impactLevel" | "status">[],
|
||||
dailyBudget: number | null,
|
||||
): PlanningMeterSnapshot {
|
||||
const plannedPoints = calculatePlannedPointsTotal(activities);
|
||||
const totalPoints = calculateActivityPointsTotal(activities);
|
||||
|
||||
if (dailyBudget === null) {
|
||||
return {
|
||||
plannedPoints,
|
||||
totalPoints,
|
||||
activityCount: activities.length,
|
||||
dailyBudget: null,
|
||||
remainingBudget: null,
|
||||
|
|
@ -86,11 +86,11 @@ export function calculatePlanningMeterSnapshot(
|
|||
};
|
||||
}
|
||||
|
||||
const remainingBudget = dailyBudget - plannedPoints;
|
||||
const progressRatio = dailyBudget > 0 ? plannedPoints / dailyBudget : 0;
|
||||
const remainingBudget = dailyBudget - totalPoints;
|
||||
const progressRatio = dailyBudget > 0 ? totalPoints / dailyBudget : 0;
|
||||
|
||||
return {
|
||||
plannedPoints,
|
||||
totalPoints,
|
||||
activityCount: activities.length,
|
||||
dailyBudget,
|
||||
remainingBudget,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { getAuthenticatedUser } from "@/lib/auth/session";
|
||||
import type {
|
||||
ActivityCategory,
|
||||
CreateAdHocActivitySubmission,
|
||||
CreateActivitySubmission,
|
||||
ActivityImpactLevel,
|
||||
ActivityPriorityLevel,
|
||||
|
|
@ -328,6 +329,51 @@ export async function createActivityForTodayForCurrentUser(
|
|||
return mapActivityRow(data);
|
||||
}
|
||||
|
||||
export async function createAdHocActivityForTodayForCurrentUser(
|
||||
submission: CreateAdHocActivitySubmission,
|
||||
): Promise<ActivityRecord> {
|
||||
const user = await getAuthenticatedUser();
|
||||
|
||||
if (!user) {
|
||||
throw new Error("Er is geen ingelogde gebruiker beschikbaar.");
|
||||
}
|
||||
|
||||
const profileBundle = await ensureProfileBundleForCurrentUser();
|
||||
|
||||
if (!profileBundle) {
|
||||
throw new Error("Profielbundle ontbreekt voor de huidige gebruiker.");
|
||||
}
|
||||
|
||||
const activityDate = getLocalDateForTimezone(profileBundle.profile.timezone);
|
||||
const supabase = await createClient();
|
||||
|
||||
await assertCategoryExists(supabase, submission.categoryId);
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from("activities")
|
||||
.insert({
|
||||
user_id: user.id,
|
||||
activity_date: activityDate,
|
||||
source: "ad_hoc",
|
||||
status: "completed",
|
||||
name: submission.name,
|
||||
category_id: submission.categoryId,
|
||||
duration_minutes: submission.durationMinutes,
|
||||
impact_level: submission.impactLevel,
|
||||
priority_level: "normaal",
|
||||
skip_reason_id: null,
|
||||
notes: null,
|
||||
})
|
||||
.select(ACTIVITY_COLUMNS)
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
throw new Error(`Ongeplande activiteit kon niet worden opgeslagen: ${error.message}`);
|
||||
}
|
||||
|
||||
return mapActivityRow(data);
|
||||
}
|
||||
|
||||
export async function updateActivityStatusForTodayForCurrentUser(
|
||||
submission: UpdateActivityStatusSubmission,
|
||||
): Promise<ActivityRecord> {
|
||||
|
|
|
|||
|
|
@ -53,6 +53,13 @@ export type CreateActivitySubmission = {
|
|||
priorityLevel: ActivityPriorityLevel;
|
||||
};
|
||||
|
||||
export type CreateAdHocActivitySubmission = {
|
||||
name: string;
|
||||
categoryId: string;
|
||||
durationMinutes: number;
|
||||
impactLevel: ActivityImpactLevel;
|
||||
};
|
||||
|
||||
export type UpdateActivityStatusSubmission = {
|
||||
activityId: string;
|
||||
status: ActivityStatus;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue