feat: add refresh functionality for bug reports and improve pagination logic
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import type { PageServerLoad } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
import { db } from '$lib/server/db';
|
import { db } from '$lib/server/db';
|
||||||
import { bugReports, bugReportFiles } from '$lib/schema';
|
import { bugReports, bugReportFiles } from '$lib/schema';
|
||||||
import { eq, like, or, count, sql, desc } from 'drizzle-orm';
|
import { eq, like, or, count, sql, desc, and } from 'drizzle-orm';
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ url }) => {
|
export const load: PageServerLoad = async ({ url }) => {
|
||||||
const page = Math.max(1, Number(url.searchParams.get('page')) || 1);
|
const page = Math.max(1, Number(url.searchParams.get('page')) || 1);
|
||||||
@@ -10,44 +10,31 @@ export const load: PageServerLoad = async ({ url }) => {
|
|||||||
const search = url.searchParams.get('search') || '';
|
const search = url.searchParams.get('search') || '';
|
||||||
|
|
||||||
const conditions = [];
|
const conditions = [];
|
||||||
|
|
||||||
if (status && ['new', 'in_review', 'resolved', 'closed'].includes(status)) {
|
if (status && ['new', 'in_review', 'resolved', 'closed'].includes(status)) {
|
||||||
conditions.push(eq(bugReports.status, status as 'new' | 'in_review' | 'resolved' | 'closed'));
|
conditions.push(eq(bugReports.status, status as 'new' | 'in_review' | 'resolved' | 'closed'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (search) {
|
if (search) {
|
||||||
const pattern = `%${search}%`;
|
|
||||||
conditions.push(
|
conditions.push(
|
||||||
or(
|
or(
|
||||||
like(bugReports.hostname, pattern),
|
like(bugReports.hostname, `%${search}%`),
|
||||||
like(bugReports.os_user, pattern),
|
like(bugReports.os_user, `%${search}%`),
|
||||||
like(bugReports.name, pattern),
|
like(bugReports.name, `%${search}%`),
|
||||||
like(bugReports.email, pattern)
|
like(bugReports.email, `%${search}%`)
|
||||||
)!
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const where = conditions.length > 0
|
const where = conditions.length > 0 ? and(...conditions) : undefined;
|
||||||
? conditions.length === 1
|
|
||||||
? conditions[0]
|
|
||||||
: sql`${conditions[0]} AND ${conditions[1]}`
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const [totalResult] = await db
|
// Get total count
|
||||||
.select({ count: count() })
|
const [{ total }] = await db
|
||||||
|
.select({ total: count() })
|
||||||
.from(bugReports)
|
.from(bugReports)
|
||||||
.where(where);
|
.where(where);
|
||||||
|
|
||||||
const total = totalResult.count;
|
// Get paginated reports with file count
|
||||||
const totalPages = Math.max(1, Math.ceil(total / pageSize));
|
|
||||||
|
|
||||||
const fileCountSubquery = db
|
|
||||||
.select({
|
|
||||||
report_id: bugReportFiles.report_id,
|
|
||||||
file_count: count().as('file_count')
|
|
||||||
})
|
|
||||||
.from(bugReportFiles)
|
|
||||||
.groupBy(bugReportFiles.report_id)
|
|
||||||
.as('fc');
|
|
||||||
|
|
||||||
const reports = await db
|
const reports = await db
|
||||||
.select({
|
.select({
|
||||||
id: bugReports.id,
|
id: bugReports.id,
|
||||||
@@ -57,21 +44,27 @@ export const load: PageServerLoad = async ({ url }) => {
|
|||||||
os_user: bugReports.os_user,
|
os_user: bugReports.os_user,
|
||||||
status: bugReports.status,
|
status: bugReports.status,
|
||||||
created_at: bugReports.created_at,
|
created_at: bugReports.created_at,
|
||||||
file_count: sql<number>`COALESCE(${fileCountSubquery.file_count}, 0)`.as('file_count')
|
file_count: count(bugReportFiles.id)
|
||||||
})
|
})
|
||||||
.from(bugReports)
|
.from(bugReports)
|
||||||
.leftJoin(fileCountSubquery, eq(bugReports.id, fileCountSubquery.report_id))
|
.leftJoin(bugReportFiles, eq(bugReports.id, bugReportFiles.report_id))
|
||||||
.where(where)
|
.where(where)
|
||||||
|
.groupBy(bugReports.id)
|
||||||
.orderBy(desc(bugReports.created_at))
|
.orderBy(desc(bugReports.created_at))
|
||||||
.limit(pageSize)
|
.limit(pageSize)
|
||||||
.offset((page - 1) * pageSize);
|
.offset((page - 1) * pageSize);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
reports: reports.map((r) => ({
|
reports,
|
||||||
...r,
|
pagination: {
|
||||||
created_at: r.created_at.toISOString()
|
page,
|
||||||
})),
|
pageSize,
|
||||||
pagination: { page, pageSize, total, totalPages },
|
total,
|
||||||
filters: { status, search }
|
totalPages: Math.ceil(total / pageSize)
|
||||||
|
},
|
||||||
|
filters: {
|
||||||
|
status,
|
||||||
|
search
|
||||||
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto, invalidateAll } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { statusColors, statusLabels, formatDate } from '$lib/utils';
|
import { statusColors, statusLabels, formatDate } from '$lib/utils';
|
||||||
import { Search, ChevronLeft, ChevronRight, Filter, Paperclip } from 'lucide-svelte';
|
import { Search, ChevronLeft, ChevronRight, Filter, Paperclip, RefreshCcw } from 'lucide-svelte';
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
|
|
||||||
@@ -33,6 +33,14 @@
|
|||||||
statusFilter = '';
|
statusFilter = '';
|
||||||
goto('/');
|
goto('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function refreshReports() {
|
||||||
|
try {
|
||||||
|
await invalidateAll();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to refresh reports:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
@@ -66,6 +74,13 @@
|
|||||||
<Filter class="h-4 w-4" />
|
<Filter class="h-4 w-4" />
|
||||||
Filter
|
Filter
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
onclick={refreshReports}
|
||||||
|
class="inline-flex items-center gap-1.5 rounded-md bg-primary px-3 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90"
|
||||||
|
>
|
||||||
|
<RefreshCcw class="h-4 w-4" />
|
||||||
|
Filter
|
||||||
|
</button>
|
||||||
{#if data.filters.search || data.filters.status}
|
{#if data.filters.search || data.filters.status}
|
||||||
<button
|
<button
|
||||||
onclick={clearFilters}
|
onclick={clearFilters}
|
||||||
|
|||||||
13
dashboard/src/routes/api/reports/refresh/+server.ts
Normal file
13
dashboard/src/routes/api/reports/refresh/+server.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import type { RequestHandler } from './$types';
|
||||||
|
import { json } from '@sveltejs/kit';
|
||||||
|
import { db } from '$lib/server/db';
|
||||||
|
import { bugReports } from '$lib/schema';
|
||||||
|
import { count } from 'drizzle-orm';
|
||||||
|
|
||||||
|
export const GET: RequestHandler = async () => {
|
||||||
|
const [{ total }] = await db
|
||||||
|
.select({ total: count() })
|
||||||
|
.from(bugReports);
|
||||||
|
|
||||||
|
return json({ total });
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user