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:
+21
-10
@@ -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 ? (
|
||||
|
||||
Reference in New Issue
Block a user