feat: p2 batch 28-31 reminders lists comments bang stats
Calendar events support multiple reminder offsets with presets and per-user defaults. Lists index adds inline task entry and list property editing. Generic entity comments on list detail. New bangs.stats dashboard widget. Closes Gitea #28, #29, #30, #31. Migrations 0022 and 0023.
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
"use client";
|
||||
|
||||
import { Plus, X } from "lucide-react";
|
||||
import { useMemo, useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import {
|
||||
REMINDER_PRESETS,
|
||||
customOffsetToMinutes,
|
||||
formatReminderOffset,
|
||||
normalizeReminderOffsets,
|
||||
type ReminderOffsetUnit,
|
||||
} from "@/lib/reminder-offsets";
|
||||
|
||||
type Props = {
|
||||
offsets: number[];
|
||||
onChange: (offsets: number[]) => void;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export function ReminderPicker({ offsets, onChange, disabled }: Props) {
|
||||
const [customValue, setCustomValue] = useState("1");
|
||||
const [customUnit, setCustomUnit] = useState<ReminderOffsetUnit>("hours");
|
||||
const normalized = useMemo(() => normalizeReminderOffsets(offsets), [offsets]);
|
||||
|
||||
function addOffset(minutes: number) {
|
||||
onChange(normalizeReminderOffsets([...normalized, minutes]));
|
||||
}
|
||||
|
||||
function removeOffset(minutes: number) {
|
||||
onChange(normalized.filter((o) => o !== minutes));
|
||||
}
|
||||
|
||||
function addCustom() {
|
||||
const parsed = Number.parseInt(customValue, 10);
|
||||
if (!Number.isFinite(parsed) || parsed < 0) return;
|
||||
addOffset(customOffsetToMinutes(parsed, customUnit));
|
||||
}
|
||||
|
||||
const availablePresets = REMINDER_PRESETS.filter((p) => !normalized.includes(p.offsetMinutes));
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<Label>Reminders</Label>
|
||||
{normalized.length === 0 ? (
|
||||
<p className="text-[13px] text-[var(--ink-mute)]">No reminders set.</p>
|
||||
) : (
|
||||
<ul className="space-y-1.5">
|
||||
{normalized.map((offset) => (
|
||||
<li
|
||||
key={offset}
|
||||
className="flex items-center justify-between gap-2 rounded-md border border-[var(--hair)] px-2.5 py-1.5 text-[13px]"
|
||||
>
|
||||
<span>{formatReminderOffset(offset)}</span>
|
||||
<Button
|
||||
type="button"
|
||||
size="icon-sm"
|
||||
variant="ghost"
|
||||
aria-label={`Remove ${formatReminderOffset(offset)}`}
|
||||
disabled={disabled}
|
||||
onClick={() => removeOffset(offset)}
|
||||
>
|
||||
<X className="size-3.5" />
|
||||
</Button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
|
||||
{availablePresets.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{availablePresets.map((preset) => (
|
||||
<Button
|
||||
key={preset.offsetMinutes}
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
disabled={disabled}
|
||||
onClick={() => addOffset(preset.offsetMinutes)}
|
||||
>
|
||||
<Plus className="size-3" />
|
||||
{preset.label}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid gap-2 sm:grid-cols-[80px_120px_auto] sm:items-end">
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="reminder-custom-value">Custom</Label>
|
||||
<Input
|
||||
id="reminder-custom-value"
|
||||
type="number"
|
||||
min={0}
|
||||
value={customValue}
|
||||
disabled={disabled}
|
||||
onChange={(e) => setCustomValue(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="reminder-custom-unit">Unit</Label>
|
||||
<Select
|
||||
value={customUnit}
|
||||
onValueChange={(value) => setCustomUnit(value as ReminderOffsetUnit)}
|
||||
disabled={disabled}
|
||||
>
|
||||
<SelectTrigger id="reminder-custom-unit">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="minutes">Minutes</SelectItem>
|
||||
<SelectItem value="hours">Hours</SelectItem>
|
||||
<SelectItem value="days">Days</SelectItem>
|
||||
<SelectItem value="weeks">Weeks</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<Button type="button" variant="secondary" disabled={disabled} onClick={addCustom}>
|
||||
Add custom
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user