chore(repo): initialize planarchy workspace

This commit is contained in:
2026-03-14 14:31:09 +01:00
commit dd55d0e78b
769 changed files with 166461 additions and 0 deletions
@@ -0,0 +1,202 @@
"use client";
import { RecurrenceFrequency } from "@planarchy/shared";
import type { RecurrencePattern } from "@planarchy/shared";
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</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</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)</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>
);
}