chore(repo): initialize planarchy workspace
This commit is contained in:
@@ -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 (1–31)</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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user