Files
Nexus/apps/web/src/components/allocations/RecurrenceEditor.tsx
T
Hartmut cd78f72f33 chore: full technical rename planarchy → capakraken
Complete rename of all technical identifiers across the codebase:

Package names (11 packages):
- @planarchy/* → @capakraken/* in all package.json, tsconfig, imports

Import statements: 277 files, 548 occurrences replaced

Database & Docker:
- PostgreSQL user/db: planarchy → capakraken
- Docker volumes: planarchy_pgdata → capakraken_pgdata
- Connection strings updated in docker-compose, .env, CI

CI/CD:
- GitHub Actions workflow: all filter commands updated
- Test database credentials updated

Infrastructure:
- Redis channel: planarchy:sse → capakraken:sse
- Logger service name: planarchy-api → capakraken-api
- Anonymization seed updated
- Start/stop/restart scripts updated

Test data:
- Seed emails: @planarchy.dev → @capakraken.dev
- E2E test credentials: all 11 spec files updated
- Email defaults: @planarchy.app → @capakraken.app
- localStorage keys: planarchy_* → capakraken_*

Documentation: 30+ .md files updated

Verification:
- pnpm install: workspace resolution works
- TypeScript: only pre-existing TS2589 (no new errors)
- Engine: 310/310 tests pass
- Staffing: 37/37 tests pass

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-03-27 13:18:09 +01:00

204 lines
7.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { RecurrenceFrequency } from "@capakraken/shared";
import type { RecurrencePattern } from "@capakraken/shared";
import { InfoTooltip } from "~/components/ui/InfoTooltip.js";
const WEEKDAY_LABELS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
interface RecurrenceEditorProps {
value: RecurrencePattern | undefined;
onChange: (pattern: RecurrencePattern | undefined) => void;
}
export function RecurrenceEditor({ value, onChange }: RecurrenceEditorProps) {
const freq = value?.frequency ?? RecurrenceFrequency.WEEKLY;
function update(patch: Partial<RecurrencePattern>) {
onChange({ ...value, frequency: freq, ...patch });
}
function setFrequency(f: RecurrenceFrequency) {
// Reset pattern-specific fields when switching frequency
onChange({ frequency: f });
}
function toggleWeekday(dow: number) {
const current = value?.weekdays ?? [];
const next = current.includes(dow)
? current.filter((d) => d !== dow)
: [...current, dow].sort((a, b) => a - b);
update({ weekdays: next });
}
const inputClass =
"px-2 py-1 border border-gray-300 dark:border-gray-600 rounded text-sm focus:outline-none focus:ring-2 focus:ring-brand-500 dark:bg-gray-900 dark:text-gray-100";
const labelClass = "text-xs font-medium text-gray-600 dark:text-gray-400 block mb-1";
return (
<div className="space-y-3 p-3 bg-gray-50 dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-700">
{/* Frequency selector */}
<div>
<span className={labelClass}>Frequency<InfoTooltip content="How often the allocation repeats: weekly, biweekly, monthly, or a custom pattern." /></span>
<div className="flex gap-2 flex-wrap">
{Object.values(RecurrenceFrequency).map((f) => (
<button
key={f}
type="button"
onClick={() => setFrequency(f)}
className={`px-3 py-1 text-xs rounded-full border transition-colors ${
freq === f
? "bg-brand-600 text-white border-brand-600"
: "border-gray-300 dark:border-gray-600 text-gray-600 dark:text-gray-400 hover:border-brand-400"
}`}
>
{f === RecurrenceFrequency.WEEKLY
? "Weekly"
: f === RecurrenceFrequency.BIWEEKLY
? "Biweekly"
: f === RecurrenceFrequency.MONTHLY
? "Monthly"
: "Custom"}
</button>
))}
</div>
</div>
{/* Weekday picker — WEEKLY and BIWEEKLY */}
{(freq === RecurrenceFrequency.WEEKLY || freq === RecurrenceFrequency.BIWEEKLY) && (
<div>
<span className={labelClass}>Days of week<InfoTooltip content="Select which days of the week this allocation is active. Hours per day applies only on selected days." /></span>
<div className="flex gap-1">
{WEEKDAY_LABELS.map((label, dow) => {
const selected = (value?.weekdays ?? []).includes(dow);
return (
<button
key={dow}
type="button"
onClick={() => toggleWeekday(dow)}
className={`w-9 h-9 text-xs rounded-full border font-medium transition-colors ${
selected
? "bg-brand-600 text-white border-brand-600"
: "border-gray-300 dark:border-gray-600 text-gray-600 dark:text-gray-400 hover:border-brand-400"
}`}
>
{label}
</button>
);
})}
</div>
</div>
)}
{/* Biweekly interval */}
{freq === RecurrenceFrequency.BIWEEKLY && (
<div>
<label className={labelClass}>Every N weeks</label>
<input
type="number"
min={2}
max={8}
value={value?.interval ?? 2}
onChange={(e) => update({ interval: Number(e.target.value) })}
className={`${inputClass} w-24`}
/>
</div>
)}
{/* Monthly — day of month */}
{freq === RecurrenceFrequency.MONTHLY && (
<div>
<label className={labelClass}>Day of month (131)</label>
<input
type="number"
min={1}
max={31}
value={value?.monthDay ?? 1}
onChange={(e) => update({ monthDay: Number(e.target.value) })}
className={`${inputClass} w-24`}
/>
</div>
)}
{/* Custom — hoursPerDay override */}
{freq === RecurrenceFrequency.CUSTOM && (
<div>
<label className={labelClass}>Hours per active day</label>
<input
type="number"
min={0.5}
max={24}
step={0.5}
value={value?.hoursPerDay ?? 8}
onChange={(e) => update({ hoursPerDay: Number(e.target.value) })}
className={`${inputClass} w-24`}
/>
</div>
)}
{/* Optional hours override for WEEKLY/BIWEEKLY/MONTHLY */}
{freq !== RecurrenceFrequency.CUSTOM && (
<div>
<label className={labelClass}>Hours per recurring day (optional override)<InfoTooltip content="Override the allocation's default hours for recurring days only. Leave empty to use the allocation's hours/day." /></label>
<input
type="number"
min={0.5}
max={24}
step={0.5}
placeholder="Use allocation default"
value={value?.hoursPerDay ?? ""}
onChange={(e) => {
const next = { ...value, frequency: freq } as RecurrencePattern;
if (e.target.value === "") {
delete next.hoursPerDay;
} else {
next.hoursPerDay = Number(e.target.value);
}
onChange(next);
}}
className={`${inputClass} w-40`}
/>
</div>
)}
{/* Optional date range overrides */}
<div className="grid grid-cols-2 gap-3">
<div>
<label className={labelClass}>Recurrence start (optional)</label>
<input
type="date"
value={value?.startDate ?? ""}
onChange={(e) => {
const next = { ...value, frequency: freq } as RecurrencePattern;
if (e.target.value) {
next.startDate = e.target.value;
} else {
delete next.startDate;
}
onChange(next);
}}
className={inputClass}
/>
</div>
<div>
<label className={labelClass}>Recurrence end (optional)</label>
<input
type="date"
value={value?.endDate ?? ""}
onChange={(e) => {
const next = { ...value, frequency: freq } as RecurrencePattern;
if (e.target.value) {
next.endDate = e.target.value;
} else {
delete next.endDate;
}
onChange(next);
}}
className={inputClass}
/>
</div>
</div>
</div>
);
}