Require a due date on all reminder creation (v0.1.0:103)

A date-less reminder has no urgency — it lands in the "Later"/"No date" bucket,
out of the overdue/today/this-week rollups and the daily digest — so every
create flow now pre-fills the due date to +1 week (editable) and blocks an empty
save. Shared reminderDefaultDue() helper; edit paths also pre-fill the default
for legacy date-less reminders.

Surfaces:
- Mobile: add-investor sheet (date auto-fills when you start the optional
  reminder), standalone Reminders "New reminder", Grid-detail "Set a reminder".
- Desktop: Reminders page "+ New reminder", grid reminder modal.

Server still accepts a null due_date by design (bot/automation callers); this is
a human-UI requirement. Frontend-only; no schema/migration/dependency change.
This commit is contained in:
Keysat
2026-06-20 16:51:03 -05:00
parent fea88b6557
commit d6250f74d0
5 changed files with 53 additions and 18 deletions
+21 -10
View File
@@ -4953,7 +4953,7 @@
const [onlyMine, setOnlyMine] = useState(false);
const [users, setUsers] = useState([]);
const [showCreate, setShowCreate] = useState(false);
const [createForm, setCreateForm] = useState({ title: '', due_date: '', details: '', investor_name: '', assignee_id: '' });
const [createForm, setCreateForm] = useState({ title: '', due_date: reminderDefaultDue(), details: '', investor_name: '', assignee_id: '' });
const [creating, setCreating] = useState(false);
const [editing, setEditing] = useState(null);
const [editForm, setEditForm] = useState({ title: '', due_date: '', details: '', status: 'open', assignee_id: '' });
@@ -5021,6 +5021,7 @@
const submitCreate = async () => {
const title = (createForm.title || '').trim();
if (!title) { onShowToast('A reminder needs a title', 'error'); return; }
if (!createForm.due_date) { onShowToast('A reminder needs a due date', 'error'); return; }
setCreating(true);
try {
await api('/api/reminders', { method: 'POST', body: JSON.stringify({
@@ -5029,7 +5030,7 @@
}) }, token);
onShowToast('Reminder created', 'success');
setShowCreate(false);
setCreateForm({ title: '', due_date: '', details: '', investor_name: '', assignee_id: '' });
setCreateForm({ title: '', due_date: reminderDefaultDue(), details: '', investor_name: '', assignee_id: '' });
load();
} catch (err) { onShowToast(getErrorMessage(err, 'Create failed'), 'error'); }
finally { setCreating(false); }
@@ -5063,7 +5064,7 @@
<div className="page-container">
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '16px', flexWrap: 'wrap', gap: '10px' }}>
<h2 className="section-title" style={{ margin: 0 }}>Reminders</h2>
<button type="button" onClick={() => setShowCreate(true)}>+ New reminder</button>
<button type="button" onClick={() => { setCreateForm((f) => ({ ...f, due_date: f.due_date || reminderDefaultDue() })); setShowCreate(true); }}>+ New reminder</button>
</div>
<div style={{ display: 'flex', gap: '12px', alignItems: 'center', marginBottom: '16px', flexWrap: 'wrap' }}>
@@ -5211,6 +5212,11 @@
const mk = (days) => { const d = new Date(); d.setHours(0, 0, 0, 0); d.setDate(d.getDate() + days); return d.toISOString().slice(0, 10); };
return [['Tomorrow', mk(1)], ['In 3 days', mk(3)], ['1 week', mk(7)], ['2 weeks', mk(14)]];
};
// Default reminder due date — one week out, local-midnight → YYYY-MM-DD (same convention as
// the snooze presets). Every create flow pre-fills this and blocks an empty save: a reminder
// needs a date or it has no urgency (it falls to the "Later"/"No date" bucket, out of the
// overdue/today/this-week rollups + the daily digest).
const reminderDefaultDue = () => { const d = new Date(); d.setHours(0, 0, 0, 0); d.setDate(d.getDate() + 7); return d.toISOString().slice(0, 10); };
// Active-reminder bucket (Overdue/Today/This week/Later, dc :318) — split by due-date delta.
// Terminal items (done/cancelled) are filtered out before bucketing and rendered separately.
const reminderBucket = (r) => {
@@ -5384,12 +5390,12 @@
}, [investors, investorQuery]);
const openCreate = () => {
setForm({ title: '', due_date: '', details: '', investor_name: '', investor_source_row_id: '', assignee_id: '', status: 'open' });
setForm({ title: '', due_date: reminderDefaultDue(), details: '', investor_name: '', investor_source_row_id: '', assignee_id: '', status: 'open' });
setEditing({ create: true });
};
const openEdit = (r) => {
setForm({
title: r.title || '', due_date: (r.due_date || '').slice(0, 10), details: r.details || '',
title: r.title || '', due_date: (r.due_date || '').slice(0, 10) || reminderDefaultDue(), details: r.details || '',
investor_name: r.investor_name || '', investor_source_row_id: '', assignee_id: r.assignee_id || '', status: r.status || 'open',
});
setEditing(r);
@@ -5399,6 +5405,7 @@
const submit = async () => {
const title = (form.title || '').trim();
if (!title) { onShowToast('A reminder needs a title', 'error'); return; }
if (!form.due_date) { onShowToast('A reminder needs a due date', 'error'); return; }
setBusy(true);
try {
if (editing && editing.create) {
@@ -8374,7 +8381,7 @@
const openReminderModal = async (row) => {
if (!row) return;
setReminderContext({ rowId: row.id, investorName: row.investor_name || '' });
setReminderForm({ title: '', due_date: '', details: '' });
setReminderForm({ title: '', due_date: reminderDefaultDue(), details: '' });
setReminderList([]);
setShowReminderModal(true);
await loadReminders(row.id);
@@ -8384,6 +8391,7 @@
if (!reminderContext?.rowId) return;
const title = (reminderForm.title || '').trim();
if (!title) { onShowToast('A reminder needs a title', 'error'); return; }
if (!reminderForm.due_date) { onShowToast('A reminder needs a due date', 'error'); return; }
setReminderSubmitting(true);
try {
await api('/api/reminders', {
@@ -8397,7 +8405,7 @@
}),
}, token);
onShowToast('Reminder set', 'success');
setReminderForm({ title: '', due_date: '', details: '' });
setReminderForm({ title: '', due_date: reminderDefaultDue(), details: '' });
await loadReminders(reminderContext.rowId);
} catch (err) {
onShowToast(getErrorMessage(err, 'Failed to set reminder'), 'error');
@@ -10560,6 +10568,7 @@
const submitReminder = async () => {
const row = selectedRow; if (!row) return;
if (!String(reminderForm.title || '').trim()) { onShowToast('A reminder needs a title', 'error'); return; }
if (!reminderForm.due_date) { onShowToast('A reminder needs a due date', 'error'); return; }
setBusy(true);
try {
if (reminder && reminder.id) {
@@ -10595,6 +10604,8 @@
const cName = createForm.contactName.trim();
if (!name) { onShowToast('Investor name is required', 'error'); return; }
if (!cName) { onShowToast('Add at least one contact name', 'error'); return; }
// A reminder needs a date (it's pre-filled when you start one; block if it was cleared).
if (createForm.reminderTitle.trim() && !createForm.reminderDue) { onShowToast('Set a due date for the reminder', 'error'); return; }
setBusy(true);
try {
// The one-row create path: log-communication finds-or-creates the investor + first
@@ -10802,7 +10813,7 @@
</div>
<div className="sheet-field">
<label className="sheet-field-label">Reminder (optional)</label>
<input className="sheet-input" value={createForm.reminderTitle} onChange={(e) => setCreateForm((f) => ({ ...f, reminderTitle: e.target.value }))} placeholder="e.g. Send Fund III deck" />
<input className="sheet-input" value={createForm.reminderTitle} onChange={(e) => setCreateForm((f) => ({ ...f, reminderTitle: e.target.value, reminderDue: (e.target.value.trim() && !f.reminderDue) ? reminderDefaultDue() : f.reminderDue }))} placeholder="e.g. Send Fund III deck" />
</div>
{createForm.reminderTitle.trim() && (
<div className="sheet-field">
@@ -10889,8 +10900,8 @@
<div className="fs-section-label">Reminder</div>
<button className="detail-tap-card" disabled={reminder === undefined} onClick={() => {
setReminderForm(reminder
? { title: reminder.title || '', due_date: (reminder.due_date || '').slice(0, 10), details: reminder.details || '' }
: { title: '', due_date: '', details: '' });
? { title: reminder.title || '', due_date: (reminder.due_date || '').slice(0, 10) || reminderDefaultDue(), details: reminder.details || '' }
: { title: '', due_date: reminderDefaultDue(), details: '' });
setSheet('reminder');
}}>
{reminder === undefined ? (