feat: rebrand AgendaPro -> AgendaMax + calendar fixes + current-week demo seed
- Rebrand all user-facing text to AgendaMax - Fix sidebar scrolling: card container no longer overflows html (flex h-full min-h-0) - Fix calendar toolbar hover: active buttons keep readable contrast - Sticky calendar toolbar (month/week/day always visible on scroll) - Seed guarantees 2-4 appointments per day for current week (Mon-Sat)
This commit is contained in:
@@ -2,6 +2,7 @@ import { Router } from "express";
|
||||
import { db } from "../db.ts";
|
||||
import type { AuthedRequest } from "../lib/auth.ts";
|
||||
import { err } from "../lib/auth.ts";
|
||||
import { bizDayBoundsSqlite } from "../lib/time.ts";
|
||||
|
||||
export const notificationsRouter = Router();
|
||||
|
||||
@@ -30,7 +31,10 @@ function buildMessage(biz: any, appt: any, kind: string): string {
|
||||
export function scheduleReminders(businessId: number) {
|
||||
const biz = db.prepare(`SELECT * FROM businesses WHERE id = ?`).get(businessId) as any;
|
||||
if (!biz) return;
|
||||
// upcoming scheduled appointments in next 7 days
|
||||
// upcoming scheduled appointments in next 7 days (UTC instants — the window is
|
||||
// instant-relative, so we bind clean UTC values instead of SQLite's date('now')).
|
||||
const nowUtc = new Date().toISOString().slice(0, 19) + "Z";
|
||||
const plus7Utc = new Date(Date.now() + 7 * 86400000).toISOString().slice(0, 19) + "Z";
|
||||
const appts = db
|
||||
.prepare(
|
||||
`SELECT a.id, a.start_at, a.status, a.client_id, c.name client_name, c.phone, c.email,
|
||||
@@ -40,10 +44,10 @@ export function scheduleReminders(businessId: number) {
|
||||
JOIN services s ON s.id = a.service_id
|
||||
LEFT JOIN employees e ON e.id = a.employee_id
|
||||
WHERE a.business_id = ? AND a.status = 'scheduled'
|
||||
AND a.start_at >= datetime('now') AND a.start_at <= datetime('now','+7 days')
|
||||
AND a.start_at >= ? AND a.start_at <= ?
|
||||
ORDER BY a.start_at ASC`
|
||||
)
|
||||
.all(businessId) as any[];
|
||||
.all(businessId, nowUtc, plus7Utc) as any[];
|
||||
// delete pending reminders that no longer have a matching upcoming scheduled appt
|
||||
db.prepare(
|
||||
`DELETE FROM notifications WHERE business_id = ? AND status = 'pending' AND kind IN ('reminder','confirmation')`
|
||||
@@ -133,7 +137,12 @@ notificationsRouter.post("/:id/cancel", (req: AuthedRequest, res) => {
|
||||
|
||||
notificationsRouter.get("/stats", (req: AuthedRequest, res) => {
|
||||
const bid = req.user!.business_id as number;
|
||||
const scalar = (sql: string) => (db.prepare(sql).get(bid) as any)?.v ?? 0;
|
||||
const tz =
|
||||
(db.prepare(`SELECT timezone t FROM businesses WHERE id = ?`).get(bid) as { t?: string } | undefined)?.t ||
|
||||
"America/Mexico_City";
|
||||
// tz-correct 30-day window (business-local). created_at is mixed-format, so wrap with datetime().
|
||||
const since30 = bizDayBoundsSqlite(tz, -30).start;
|
||||
const scalar = (sql: string, ...args: any[]) => (db.prepare(sql).get(bid, ...args) as any)?.v ?? 0;
|
||||
res.json({
|
||||
pending: scalar(`SELECT COUNT(*) v FROM notifications WHERE business_id = ? AND status='pending'`),
|
||||
sent: scalar(`SELECT COUNT(*) v FROM notifications WHERE business_id = ? AND status='sent'`),
|
||||
@@ -141,7 +150,9 @@ notificationsRouter.get("/stats", (req: AuthedRequest, res) => {
|
||||
`SELECT COUNT(*) v FROM notifications WHERE business_id = ? AND status='pending' AND kind='reminder' AND send_at >= datetime('now')`
|
||||
),
|
||||
no_show_rate: scalar(
|
||||
`SELECT COALESCE(ROUND(100.0 * SUM(CASE WHEN status='no_show' THEN 1 ELSE 0 END) / NULLIF(COUNT(*),0),1),0) v FROM appointments WHERE business_id = ? AND created_at >= datetime('now','-30 days')`
|
||||
`SELECT COALESCE(ROUND(100.0 * SUM(CASE WHEN status='no_show' THEN 1 ELSE 0 END) / NULLIF(COUNT(*),0),1),0) v
|
||||
FROM appointments WHERE business_id = ? AND datetime(created_at) >= ?`,
|
||||
since30
|
||||
),
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user