Compare commits
2 commits
10c68657a7
...
5f4ccb9c56
| Author | SHA1 | Date | |
|---|---|---|---|
| 5f4ccb9c56 | |||
| f32403ee35 |
7 changed files with 37 additions and 35 deletions
|
|
@ -49,8 +49,8 @@ export async function GET() {
|
|||
}>(
|
||||
`
|
||||
SELECT
|
||||
COUNT(*) FILTER (WHERE ((create_date AT TIME ZONE 'UTC') AT TIME ZONE $1)::date = (NOW() AT TIME ZONE 'UTC' AT TIME ZONE $1)::date)::text AS opened_today,
|
||||
COUNT(*) FILTER (WHERE ((completed_date AT TIME ZONE 'UTC') AT TIME ZONE $1)::date = (NOW() AT TIME ZONE 'UTC' AT TIME ZONE $1)::date)::text AS resolved_today,
|
||||
COUNT(*) FILTER (WHERE ((create_date AT TIME ZONE 'UTC') AT TIME ZONE $1)::date = (NOW() AT TIME ZONE $1)::date)::text AS opened_today,
|
||||
COUNT(*) FILTER (WHERE ((completed_date AT TIME ZONE 'UTC') AT TIME ZONE $1)::date = (NOW() AT TIME ZONE $1)::date)::text AS resolved_today,
|
||||
COUNT(*) FILTER (WHERE completed_date IS NULL)::text AS open_total,
|
||||
COUNT(*) FILTER (
|
||||
WHERE completed_date IS NULL
|
||||
|
|
@ -68,7 +68,7 @@ export async function GET() {
|
|||
`
|
||||
SELECT COUNT(*)::text AS count
|
||||
FROM tickets
|
||||
WHERE ((create_date AT TIME ZONE 'UTC') AT TIME ZONE $1)::date = (NOW() AT TIME ZONE 'UTC' AT TIME ZONE $1)::date - INTERVAL '1 day'
|
||||
WHERE ((create_date AT TIME ZONE 'UTC') AT TIME ZONE $1)::date = (NOW() AT TIME ZONE $1)::date - INTERVAL '1 day'
|
||||
AND (is_deleted = false OR is_deleted IS NULL)
|
||||
AND company_id NOT IN (SELECT company_id FROM company_scope WHERE in_scope = false)
|
||||
`,
|
||||
|
|
@ -81,8 +81,8 @@ export async function GET() {
|
|||
FROM (
|
||||
SELECT ((completed_date AT TIME ZONE 'UTC') AT TIME ZONE $1)::date AS d, COUNT(*) AS daily_count
|
||||
FROM tickets
|
||||
WHERE ((completed_date AT TIME ZONE 'UTC') AT TIME ZONE $1)::date >= (NOW() AT TIME ZONE 'UTC' AT TIME ZONE $1)::date - INTERVAL '7 days'
|
||||
AND ((completed_date AT TIME ZONE 'UTC') AT TIME ZONE $1)::date < (NOW() AT TIME ZONE 'UTC' AT TIME ZONE $1)::date
|
||||
WHERE ((completed_date AT TIME ZONE 'UTC') AT TIME ZONE $1)::date >= (NOW() AT TIME ZONE $1)::date - INTERVAL '7 days'
|
||||
AND ((completed_date AT TIME ZONE 'UTC') AT TIME ZONE $1)::date < (NOW() AT TIME ZONE $1)::date
|
||||
AND (is_deleted = false OR is_deleted IS NULL)
|
||||
AND company_id NOT IN (SELECT company_id FROM company_scope WHERE in_scope = false)
|
||||
GROUP BY ((completed_date AT TIME ZONE 'UTC') AT TIME ZONE $1)::date
|
||||
|
|
|
|||
|
|
@ -28,8 +28,8 @@ export async function GET() {
|
|||
postgresClient.query<{ d: string; count: string }>(
|
||||
`WITH days AS (
|
||||
SELECT generate_series(
|
||||
(NOW() AT TIME ZONE 'UTC' AT TIME ZONE $1)::date - INTERVAL '${TREND_DAYS - 1} days',
|
||||
(NOW() AT TIME ZONE 'UTC' AT TIME ZONE $1)::date,
|
||||
(NOW() AT TIME ZONE $1)::date - INTERVAL '${TREND_DAYS - 1} days',
|
||||
(NOW() AT TIME ZONE $1)::date,
|
||||
INTERVAL '1 day'
|
||||
)::date AS d
|
||||
)
|
||||
|
|
@ -46,8 +46,8 @@ export async function GET() {
|
|||
postgresClient.query<{ d: string; avg_hours: string | null }>(
|
||||
`WITH days AS (
|
||||
SELECT generate_series(
|
||||
(NOW() AT TIME ZONE 'UTC' AT TIME ZONE $1)::date - INTERVAL '${TREND_DAYS - 1} days',
|
||||
(NOW() AT TIME ZONE 'UTC' AT TIME ZONE $1)::date,
|
||||
(NOW() AT TIME ZONE $1)::date - INTERVAL '${TREND_DAYS - 1} days',
|
||||
(NOW() AT TIME ZONE $1)::date,
|
||||
INTERVAL '1 day'
|
||||
)::date AS d
|
||||
)
|
||||
|
|
@ -94,7 +94,7 @@ export async function GET() {
|
|||
COUNT(DISTINCT te.ticket_id)::text AS tickets_touched
|
||||
FROM time_entries te
|
||||
LEFT JOIN resources r ON r.id = te.resource_id
|
||||
WHERE ((te.entry_date AT TIME ZONE 'UTC') AT TIME ZONE $1)::date = (NOW() AT TIME ZONE 'UTC' AT TIME ZONE $1)::date
|
||||
WHERE ((te.entry_date AT TIME ZONE 'UTC') AT TIME ZONE $1)::date = (NOW() AT TIME ZONE $1)::date
|
||||
AND te.hours_worked > 0
|
||||
GROUP BY te.resource_id, r.first_name, r.last_name, r.email
|
||||
ORDER BY SUM(te.hours_worked) DESC
|
||||
|
|
|
|||
|
|
@ -72,8 +72,8 @@ export async function GET() {
|
|||
`
|
||||
SELECT
|
||||
COUNT(*) FILTER (WHERE completed_date IS NULL)::text AS open_total,
|
||||
COUNT(*) FILTER (WHERE ((create_date AT TIME ZONE 'UTC') AT TIME ZONE $1)::date = (NOW() AT TIME ZONE 'UTC' AT TIME ZONE $1)::date)::text AS opened_today,
|
||||
COUNT(*) FILTER (WHERE ((completed_date AT TIME ZONE 'UTC') AT TIME ZONE $1)::date = (NOW() AT TIME ZONE 'UTC' AT TIME ZONE $1)::date)::text AS resolved_today,
|
||||
COUNT(*) FILTER (WHERE ((create_date AT TIME ZONE 'UTC') AT TIME ZONE $1)::date = (NOW() AT TIME ZONE $1)::date)::text AS opened_today,
|
||||
COUNT(*) FILTER (WHERE ((completed_date AT TIME ZONE 'UTC') AT TIME ZONE $1)::date = (NOW() AT TIME ZONE $1)::date)::text AS resolved_today,
|
||||
COUNT(*) FILTER (
|
||||
WHERE completed_date IS NULL
|
||||
AND due_date_time IS NOT NULL
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ export async function GET(request: NextRequest): Promise<NextResponse> {
|
|||
FROM time_entries te
|
||||
JOIN resources r ON r.id = te.resource_id AND (r.is_deleted = false OR r.is_deleted IS NULL)
|
||||
JOIN graph_users gu ON LOWER(gu.email) = LOWER(r.email)
|
||||
WHERE (te.entry_date AT TIME ZONE 'UTC' AT TIME ZONE $1) >= (NOW() AT TIME ZONE 'UTC' AT TIME ZONE $1) - INTERVAL '${interval}'
|
||||
WHERE (te.entry_date AT TIME ZONE 'UTC' AT TIME ZONE $1) >= (NOW() AT TIME ZONE $1) - INTERVAL '${interval}'
|
||||
AND (te.is_deleted = false OR te.is_deleted IS NULL)
|
||||
AND gu.account_enabled = true
|
||||
AND LOWER(gu.email) LIKE '%@wulfconsulting.%'
|
||||
|
|
|
|||
|
|
@ -50,8 +50,8 @@ export async function GET(request: NextRequest): Promise<NextResponse> {
|
|||
const sql = `
|
||||
WITH day_series AS (
|
||||
SELECT generate_series(
|
||||
((NOW() AT TIME ZONE 'UTC' AT TIME ZONE $1) - INTERVAL '${days - 1} days')::date,
|
||||
(NOW() AT TIME ZONE 'UTC' AT TIME ZONE $1)::date,
|
||||
((NOW() AT TIME ZONE $1) - INTERVAL '${days - 1} days')::date,
|
||||
(NOW() AT TIME ZONE $1)::date,
|
||||
INTERVAL '1 day'
|
||||
)::date AS day
|
||||
),
|
||||
|
|
@ -61,8 +61,8 @@ export async function GET(request: NextRequest): Promise<NextResponse> {
|
|||
JOIN resources r ON r.id = te.resource_id
|
||||
AND (r.is_deleted = false OR r.is_deleted IS NULL)
|
||||
JOIN graph_users gu ON LOWER(gu.email) = LOWER(r.email)
|
||||
WHERE (te.entry_date AT TIME ZONE 'UTC' AT TIME ZONE $1)::date >= ((NOW() AT TIME ZONE 'UTC' AT TIME ZONE $1) - INTERVAL '${days - 1} days')::date
|
||||
AND (te.entry_date AT TIME ZONE 'UTC' AT TIME ZONE $1)::date <= (NOW() AT TIME ZONE 'UTC' AT TIME ZONE $1)::date
|
||||
WHERE (te.entry_date AT TIME ZONE 'UTC' AT TIME ZONE $1)::date >= ((NOW() AT TIME ZONE $1) - INTERVAL '${days - 1} days')::date
|
||||
AND (te.entry_date AT TIME ZONE 'UTC' AT TIME ZONE $1)::date <= (NOW() AT TIME ZONE $1)::date
|
||||
AND (te.is_deleted = false OR te.is_deleted IS NULL)
|
||||
AND gu.account_enabled = true
|
||||
AND LOWER(gu.email) LIKE '%@wulfconsulting.%'
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ export async function GET() {
|
|||
COUNT(*) FILTER (WHERE status = 'Open') as current_count,
|
||||
SUM(balance) FILTER (WHERE status = 'Overdue') as overdue_balance,
|
||||
COUNT(*) FILTER (WHERE status = 'Overdue') as overdue_count,
|
||||
SUM(total_amt) FILTER (WHERE status = 'Paid' AND (txn_date AT TIME ZONE 'UTC' AT TIME ZONE $1) >= DATE_TRUNC('month', NOW() AT TIME ZONE 'UTC' AT TIME ZONE $1)) as paid_mtd,
|
||||
SUM(total_amt) FILTER (WHERE status = 'Paid' AND (txn_date AT TIME ZONE 'UTC' AT TIME ZONE $1) >= DATE_TRUNC('year', NOW() AT TIME ZONE 'UTC' AT TIME ZONE $1)) as paid_ytd
|
||||
SUM(total_amt) FILTER (WHERE status = 'Paid' AND (txn_date AT TIME ZONE 'UTC' AT TIME ZONE $1) >= DATE_TRUNC('month', NOW() AT TIME ZONE $1)) as paid_mtd,
|
||||
SUM(total_amt) FILTER (WHERE status = 'Paid' AND (txn_date AT TIME ZONE 'UTC' AT TIME ZONE $1) >= DATE_TRUNC('year', NOW() AT TIME ZONE $1)) as paid_ytd
|
||||
FROM qbo_invoices
|
||||
`,
|
||||
[tz],
|
||||
|
|
@ -32,14 +32,14 @@ export async function GET() {
|
|||
postgresClient.query(
|
||||
`
|
||||
SELECT
|
||||
SUM(balance) FILTER (WHERE status = 'Overdue' AND due_date >= (NOW() AT TIME ZONE 'UTC' AT TIME ZONE $1)::date - 30) as days_1_30,
|
||||
COUNT(*) FILTER (WHERE status = 'Overdue' AND due_date >= (NOW() AT TIME ZONE 'UTC' AT TIME ZONE $1)::date - 30) as cnt_1_30,
|
||||
SUM(balance) FILTER (WHERE status = 'Overdue' AND due_date < (NOW() AT TIME ZONE 'UTC' AT TIME ZONE $1)::date - 30
|
||||
AND due_date >= (NOW() AT TIME ZONE 'UTC' AT TIME ZONE $1)::date - 60) as days_31_60,
|
||||
COUNT(*) FILTER (WHERE status = 'Overdue' AND due_date < (NOW() AT TIME ZONE 'UTC' AT TIME ZONE $1)::date - 30
|
||||
AND due_date >= (NOW() AT TIME ZONE 'UTC' AT TIME ZONE $1)::date - 60) as cnt_31_60,
|
||||
SUM(balance) FILTER (WHERE status = 'Overdue' AND due_date < (NOW() AT TIME ZONE 'UTC' AT TIME ZONE $1)::date - 60) as days_60_plus,
|
||||
COUNT(*) FILTER (WHERE status = 'Overdue' AND due_date < (NOW() AT TIME ZONE 'UTC' AT TIME ZONE $1)::date - 60) as cnt_60_plus
|
||||
SUM(balance) FILTER (WHERE status = 'Overdue' AND due_date >= (NOW() AT TIME ZONE $1)::date - 30) as days_1_30,
|
||||
COUNT(*) FILTER (WHERE status = 'Overdue' AND due_date >= (NOW() AT TIME ZONE $1)::date - 30) as cnt_1_30,
|
||||
SUM(balance) FILTER (WHERE status = 'Overdue' AND due_date < (NOW() AT TIME ZONE $1)::date - 30
|
||||
AND due_date >= (NOW() AT TIME ZONE $1)::date - 60) as days_31_60,
|
||||
COUNT(*) FILTER (WHERE status = 'Overdue' AND due_date < (NOW() AT TIME ZONE $1)::date - 30
|
||||
AND due_date >= (NOW() AT TIME ZONE $1)::date - 60) as cnt_31_60,
|
||||
SUM(balance) FILTER (WHERE status = 'Overdue' AND due_date < (NOW() AT TIME ZONE $1)::date - 60) as days_60_plus,
|
||||
COUNT(*) FILTER (WHERE status = 'Overdue' AND due_date < (NOW() AT TIME ZONE $1)::date - 60) as cnt_60_plus
|
||||
FROM qbo_invoices
|
||||
`,
|
||||
[tz],
|
||||
|
|
@ -54,7 +54,7 @@ export async function GET() {
|
|||
postgresClient.query(
|
||||
`
|
||||
SELECT id, doc_number, txn_date, due_date, customer_ref_name, total_amt, balance, status,
|
||||
(NOW() AT TIME ZONE 'UTC' AT TIME ZONE $1)::date - due_date::date as days_overdue
|
||||
(NOW() AT TIME ZONE $1)::date - due_date::date as days_overdue
|
||||
FROM qbo_invoices
|
||||
WHERE status IN ('Open','Overdue')
|
||||
ORDER BY status DESC, balance DESC LIMIT 30
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
* Renders nothing.
|
||||
*/
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useTheme } from 'next-themes';
|
||||
import { useSession } from '@/lib/auth-client';
|
||||
|
||||
|
|
@ -19,8 +19,15 @@ type SessionUserWithTheme = { theme?: 'light' | 'dark' | 'system' | string };
|
|||
export function ThemeSessionBridge() {
|
||||
const { data: session } = useSession();
|
||||
const { theme, setTheme } = useTheme();
|
||||
// Sync server → client EXACTLY ONCE per tab session. better-auth's
|
||||
// useSession (nanostores) can re-emit on focus / store refresh, which would
|
||||
// otherwise call setTheme with the stale cached session.user.theme and
|
||||
// revert the user's just-made selection. The PUT to /api/me/theme does NOT
|
||||
// refresh the cached session, so any post-selection re-fire would clobber.
|
||||
const hasSynced = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (hasSynced.current) return;
|
||||
if (!session?.user) return;
|
||||
const serverTheme = (session.user as SessionUserWithTheme).theme;
|
||||
// Only enforce explicit choices. 'system' is the column default and may
|
||||
|
|
@ -32,12 +39,7 @@ export function ThemeSessionBridge() {
|
|||
setTheme(serverTheme);
|
||||
}
|
||||
}
|
||||
// Deps intentionally exclude `theme`. This bridge syncs server → client
|
||||
// on session establishment only. Including `theme` made the effect re-fire
|
||||
// on every user selection and revert to the stale cached
|
||||
// `session.user.theme` (the PUT to /api/me/theme does not refresh the
|
||||
// better-auth session, so user.theme remains the pre-change value and
|
||||
// "wins" against the new client value).
|
||||
hasSynced.current = true;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [session?.user?.id, setTheme]);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue