feat: Refine admin guest counting logic, increase stats refresh rate, and add guest count utility scripts.
This commit is contained in:
20
check-counts.js
Normal file
20
check-counts.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
const { PrismaClient } = require('@prisma/client');
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const total = await prisma.guest.count();
|
||||||
|
const confirmed = await prisma.guest.count({
|
||||||
|
where: {
|
||||||
|
OR: [
|
||||||
|
{ rsvpStatus: 'ACCEPTED' },
|
||||||
|
{ AND: [{ rsvpStatus: null }, { isAttending: true }] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log(`Total Guests: ${total}`);
|
||||||
|
console.log(`Confirmed Guests: ${confirmed}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
|
.catch(e => console.error(e))
|
||||||
|
.finally(async () => await prisma.$disconnect());
|
||||||
25
check-counts.ts
Normal file
25
check-counts.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { prisma } from "@/lib/db";
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log('Checking database counts...');
|
||||||
|
try {
|
||||||
|
const total = await prisma.guest.count();
|
||||||
|
const confirmed = await prisma.guest.count({
|
||||||
|
where: {
|
||||||
|
OR: [
|
||||||
|
{ rsvpStatus: 'ACCEPTED' },
|
||||||
|
{ AND: [{ rsvpStatus: 'PENDING' }, { isAttending: true }] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log(`Total Guests: ${total}`);
|
||||||
|
console.log(`Confirmed Guests: ${confirmed}`);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error querying database:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
|
.catch(e => console.error(e))
|
||||||
|
.finally(async () => await prisma.$disconnect());
|
||||||
@@ -13,7 +13,7 @@ const AdminDashboard = dynamic(
|
|||||||
// This prevents server-only code from being evaluated during bundling
|
// This prevents server-only code from being evaluated during bundling
|
||||||
if (typeof window === 'undefined') {
|
if (typeof window === 'undefined') {
|
||||||
// On server, return a no-op component that won't be used (ssr: false)
|
// On server, return a no-op component that won't be used (ssr: false)
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
default: () => {
|
default: () => {
|
||||||
if (typeof window === 'undefined') {
|
if (typeof window === 'undefined') {
|
||||||
return null;
|
return null;
|
||||||
@@ -43,8 +43,8 @@ const AdminDashboard = dynamic(
|
|||||||
const AdminLayout = dynamic(
|
const AdminLayout = dynamic(
|
||||||
() => {
|
() => {
|
||||||
if (typeof window === 'undefined') {
|
if (typeof window === 'undefined') {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
default: () => null
|
default: () => null
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return import("@/components/features/admin/admin-layout").then((mod) => ({ default: mod.AdminLayout }));
|
return import("@/components/features/admin/admin-layout").then((mod) => ({ default: mod.AdminLayout }));
|
||||||
@@ -84,7 +84,7 @@ function AdminContent() {
|
|||||||
method: 'GET',
|
method: 'GET',
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
let data;
|
let data;
|
||||||
try {
|
try {
|
||||||
@@ -126,14 +126,14 @@ function AdminContent() {
|
|||||||
// Use API route for all authentication (handles both email and username)
|
// Use API route for all authentication (handles both email and username)
|
||||||
// This avoids client-side network issues and ensures proper cookie handling
|
// This avoids client-side network issues and ensures proper cookie handling
|
||||||
const isEmail = trimmedUsername.includes('@');
|
const isEmail = trimmedUsername.includes('@');
|
||||||
|
|
||||||
const response = await fetch('/api/admin/auth', {
|
const response = await fetch('/api/admin/auth', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
[isEmail ? 'email' : 'username']: trimmedUsername,
|
[isEmail ? 'email' : 'username']: trimmedUsername,
|
||||||
password: trimmedPassword
|
password: trimmedPassword
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -141,24 +141,24 @@ function AdminContent() {
|
|||||||
try {
|
try {
|
||||||
// Read response body once
|
// Read response body once
|
||||||
const text = await response.text();
|
const text = await response.text();
|
||||||
|
|
||||||
// Check content type
|
// Check content type
|
||||||
const contentType = response.headers.get('content-type');
|
const contentType = response.headers.get('content-type');
|
||||||
if (!contentType || !contentType.includes('application/json')) {
|
if (!contentType || !contentType.includes('application/json')) {
|
||||||
// Handle non-JSON responses (e.g., HTML error pages, plain text errors)
|
// Handle non-JSON responses (e.g., HTML error pages, plain text errors)
|
||||||
const errorMessage = text.includes('Internal Server Error')
|
const errorMessage = text.includes('Internal Server Error')
|
||||||
? 'Server error occurred. Please try again or check server logs.'
|
? 'Server error occurred. Please try again or check server logs.'
|
||||||
: text.substring(0, 200) || `Server error: ${response.status} ${response.statusText}`;
|
: text.substring(0, 200) || `Server error: ${response.status} ${response.statusText}`;
|
||||||
|
|
||||||
// Only log full error in development
|
// Only log full error in development
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
console.error('Non-JSON response received:', text.substring(0, 200));
|
console.error('Non-JSON response received:', text.substring(0, 200));
|
||||||
}
|
}
|
||||||
|
|
||||||
setError(errorMessage);
|
setError(errorMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse JSON
|
// Parse JSON
|
||||||
data = text ? JSON.parse(text) : {};
|
data = text ? JSON.parse(text) : {};
|
||||||
} catch (parseError) {
|
} catch (parseError) {
|
||||||
@@ -166,11 +166,11 @@ function AdminContent() {
|
|||||||
const errorMessage = response.status >= 500
|
const errorMessage = response.status >= 500
|
||||||
? 'Server error occurred. Please try again.'
|
? 'Server error occurred. Please try again.'
|
||||||
: 'Invalid response from server. Please check your connection.';
|
: 'Invalid response from server. Please check your connection.';
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
console.error('Failed to parse response:', parseError);
|
console.error('Failed to parse response:', parseError);
|
||||||
}
|
}
|
||||||
|
|
||||||
setError(errorMessage);
|
setError(errorMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -201,7 +201,7 @@ function AdminContent() {
|
|||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
try {
|
try {
|
||||||
// Call API logout (handles Supabase sign out server-side)
|
// Call API logout (handles Supabase sign out server-side)
|
||||||
await fetch('/api/admin/auth', {
|
await fetch('/api/admin/auth', {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
});
|
});
|
||||||
@@ -365,16 +365,12 @@ function AdminContent() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSettingsClick = () => {
|
|
||||||
// Navigate to settings tab
|
|
||||||
window.location.href = '/admin?tab=settings';
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AdminStatsProvider>
|
<AdminStatsProvider>
|
||||||
<AdminLayout onLogout={handleLogout}>
|
<AdminLayout onLogout={handleLogout}>
|
||||||
<Suspense key="admin-dashboard" fallback={<div className="flex items-center justify-center min-h-screen"><div className="text-wedding-evergreen font-body">Loading dashboard...</div></div>}>
|
<Suspense key="admin-dashboard" fallback={<div className="flex items-center justify-center min-h-screen"><div className="text-wedding-evergreen font-body">Loading dashboard...</div></div>}>
|
||||||
<AdminDashboard defaultTab={activeTab} onSettingsClick={handleSettingsClick} />
|
<AdminDashboard defaultTab={activeTab} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</AdminLayout>
|
</AdminLayout>
|
||||||
</AdminStatsProvider>
|
</AdminStatsProvider>
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ async function fetchStatsFromDatabase() {
|
|||||||
{ rsvpStatus: 'ACCEPTED' },
|
{ rsvpStatus: 'ACCEPTED' },
|
||||||
{
|
{
|
||||||
AND: [
|
AND: [
|
||||||
{ OR: [{ rsvpStatus: null }, { rsvpStatus: 'PENDING' }] },
|
{ rsvpStatus: 'PENDING' },
|
||||||
{ isAttending: true }
|
{ isAttending: true }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -48,7 +48,7 @@ async function fetchStatsFromDatabase() {
|
|||||||
{ rsvpStatus: 'DECLINED' },
|
{ rsvpStatus: 'DECLINED' },
|
||||||
{
|
{
|
||||||
AND: [
|
AND: [
|
||||||
{ OR: [{ rsvpStatus: null }, { rsvpStatus: 'PENDING' }] },
|
{ rsvpStatus: 'PENDING' },
|
||||||
{ isAttending: false }
|
{ isAttending: false }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -62,7 +62,7 @@ async function fetchStatsFromDatabase() {
|
|||||||
{ rsvpStatus: 'ACCEPTED' },
|
{ rsvpStatus: 'ACCEPTED' },
|
||||||
{
|
{
|
||||||
AND: [
|
AND: [
|
||||||
{ OR: [{ rsvpStatus: null }, { rsvpStatus: 'PENDING' }] },
|
{ rsvpStatus: 'PENDING' },
|
||||||
{ isAttending: true }
|
{ isAttending: true }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,11 @@ export function AdminDashboard({
|
|||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const tabFromUrl = searchParams.get("tab") || defaultTab;
|
const tabFromUrl = searchParams.get("tab") || defaultTab;
|
||||||
const [activeTab, setActiveTab] = useState(tabFromUrl);
|
const [activeTab, setActiveTab] = useState(tabFromUrl);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log("AdminDashboard mounted");
|
||||||
|
}, []);
|
||||||
|
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
// Unified Data Hooks
|
// Unified Data Hooks
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ export function AdminStatsProvider({ children }: AdminStatsProviderProps) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
refresh(true); // Silent refresh
|
refresh(true); // Silent refresh
|
||||||
}, 60000); // 60 seconds
|
}, 5000); // 5 seconds
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, [refresh]);
|
}, [refresh]);
|
||||||
|
|||||||
Reference in New Issue
Block a user