fix(meetings): hide empty 1:1 direct calls; date from/to validation + field alignment (batch85)

#3 The 136 "Direct Call" cards: calls.js writes a scheduled_meetings row for EVERY call
   ("Direct Call" / "Group call"), which is separate from the call_history table. Those rows
   came through the scheduled-meetings path and my earlier filter never touched them. Now an
   auto call-history row is shown in Past ONLY if it produced a recording/transcript OR the
   call ever held >2 people (peak from the call log); plain 1:1s with neither are dropped.
   Removed the now-redundant callRows synthesis. Recordings stay attached (rows WITH a
   recording are always kept).
#4 Date range: from ≤ to enforced by disabling out-of-range days in each picker (can't pick a
   from after to, or a to before from). Filter controls share one height/baseline so the
   calendar icon, preset dropdown and date fields align cleanly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 13:19:12 +05:30
parent fad8a52da4
commit f708cc2f12
2 changed files with 37 additions and 34 deletions
+16 -21
View File
@@ -1244,26 +1244,21 @@ route('GET', '/api/meetings', async (req, res) => {
// • a plain 1:1 direct call is NOT listed (it's a call, not a meeting) — UNLESS it produced a
// recording/transcript, which the `synth` entries above already cover;
// • a call that ever held MORE THAN 2 people IS listed (e.g. a 1:1 that a third person joined).
// Rooms already represented by a scheduled meeting or a recording entry are skipped, so nothing doubles.
const takenRooms = new Set([...schedByRoom.keys(), ...[...unsched.values()].map((l) => l[0].room).filter(Boolean)]);
const callRows = [];
for (const c of R.callHistory.forTeam(u.team_id)) {
if (c.peak <= 2) continue; // 1:1 (or nobody) → not a meeting
if (c.room && takenRooms.has(c.room)) continue; // already listed above
let uids = []; try { uids = JSON.parse(c.uids || '[]'); } catch (_) {}
const canSee = uids.includes(u.id) || (c.group_id && R.conversations.isMember(c.group_id, u.id));
if (!canSee) continue; // only people who were actually in it
let parts = []; try { parts = JSON.parse(c.participants || '[]'); } catch (_) {}
callRows.push({
id: 'call-' + c.id, roomCode: c.room || '', title: c.title || (c.group_id ? 'Group call' : 'Meeting'),
description: '', scheduledAt: c.started_at, endedAt: c.ended_at, groupId: c.group_id || null,
groupName: c.group_id ? ((R.conversations.byId(c.group_id) || {}).name || 'Group') : null,
createdBy: null, createdByName: '', canManage: false, isHost: false,
invited: parts, participantCount: c.peak,
durationMins: Math.max(1, Math.round((c.ended_at - c.started_at) / 60000)),
status: 'past', inCall: 0, recordings: [],
});
}
// #3/#7: calls.js writes a scheduled_meetings row for EVERY call ("Direct Call" for 1:1, "Group call"
// for group). Those are the 136 "Direct Call" cards. Peak participant count per room comes from the
// call log. Rule: an auto call-history row is shown in Past ONLY if it produced a recording/transcript
// OR the call ever held MORE than 2 people. Plain 1:1s with neither are dropped. Real, user-scheduled
// meetings (any other title) are never filtered.
const peakByRoom = {};
for (const c of R.callHistory.forTeam(u.team_id)) { if (c.room) peakByRoom[c.room] = Math.max(peakByRoom[c.room] || 0, c.peak || 0); }
const CALL_TITLES = new Set(['Direct Call', 'Group call']);
const keptRows = rows.filter((m) => {
if (!CALL_TITLES.has(m.title)) return true; // a real scheduled meeting
if (m.recordings && m.recordings.length) return true; // has a recording / transcript
const peak = peakByRoom[m.roomCode] || 0;
if (peak > 2) { m.participantCount = peak; m.invited = []; return true; } // grew past 2 people
return false; // nothing to show → hide (#3)
});
// Date filter + pagination apply to PAST only (running/upcoming are small and always returned whole).
const q = new URLSearchParams(req.url.split('?')[1] || '');
@@ -1271,7 +1266,7 @@ route('GET', '/api/meetings', async (req, res) => {
const to = Number(q.get('to')) || 0;
const page = Math.max(1, Number(q.get('page')) || 1);
const pageSize = Math.min(50, Math.max(5, Number(q.get('pageSize')) || 10));
const all = rows.concat(synth, callRows);
const all = keptRows.concat(synth);
const live2 = all.filter((m) => m.status !== 'past');
let past = all.filter((m) => m.status === 'past');
if (from) past = past.filter((m) => m.scheduledAt >= from);