feat(admin): OCR admin pages — overview & model detail #265
@@ -34,7 +34,7 @@ const { trainingInfo } = $derived(data);
|
||||
</h2>
|
||||
<a
|
||||
href="/admin/ocr/global"
|
||||
class="text-xs font-medium text-brand-navy/60 transition-colors hover:text-brand-navy"
|
||||
class="text-xs font-medium text-brand-navy/60 transition-colors hover:text-brand-navy focus-visible:rounded-sm focus-visible:ring-2 focus-visible:ring-brand-navy focus-visible:outline-none"
|
||||
>
|
||||
{m.ocr_global_history_link()}
|
||||
</a>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { afterEach, describe, it, expect } from 'vitest';
|
||||
import { cleanup, render } from 'vitest-browser-svelte';
|
||||
import { page } from 'vitest/browser';
|
||||
import OcrHealthBar from './OcrHealthBar.svelte';
|
||||
|
||||
afterEach(cleanup);
|
||||
@@ -8,11 +7,13 @@ afterEach(cleanup);
|
||||
describe('OcrHealthBar', () => {
|
||||
it('shows online status when OCR service is available', async () => {
|
||||
render(OcrHealthBar, { ocrServiceAvailable: true });
|
||||
await expect.element(page.getByText(/online/i)).toBeInTheDocument();
|
||||
const dot = document.querySelector('[role="img"]');
|
||||
expect(dot?.className).toContain('bg-green-500');
|
||||
});
|
||||
|
||||
it('shows offline status when OCR service is unavailable', async () => {
|
||||
render(OcrHealthBar, { ocrServiceAvailable: false });
|
||||
await expect.element(page.getByText(/offline/i)).toBeInTheDocument();
|
||||
const dot = document.querySelector('[role="img"]');
|
||||
expect(dot?.className).toContain('bg-red-500');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -42,7 +42,10 @@ let {
|
||||
{#each senderModels as model (model.id)}
|
||||
<tr>
|
||||
<td class="border-brand-sand/50 border-b py-3">
|
||||
<a href="/admin/ocr/{model.personId}" class="text-brand-navy hover:underline">
|
||||
<a
|
||||
href="/admin/ocr/{model.personId}"
|
||||
class="text-brand-navy hover:underline focus-visible:rounded-sm focus-visible:ring-2 focus-visible:ring-brand-navy focus-visible:outline-none"
|
||||
>
|
||||
{personNames[model.personId] ?? model.personId}
|
||||
</a>
|
||||
</td>
|
||||
@@ -58,7 +61,8 @@ let {
|
||||
<td class="border-brand-sand/50 border-b py-3">
|
||||
<a
|
||||
href="/admin/ocr/{model.personId}"
|
||||
class="font-medium text-brand-navy hover:underline">{m.ocr_table_details()}</a
|
||||
class="font-medium text-brand-navy hover:underline focus-visible:rounded-sm focus-visible:ring-2 focus-visible:ring-brand-navy focus-visible:outline-none"
|
||||
>{m.ocr_table_details()}</a
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -11,7 +11,7 @@ const personName = $derived(data.history.personNames?.[params.personId] ?? 'Unkn
|
||||
<div class="flex items-center gap-4">
|
||||
<a
|
||||
href="/admin/ocr"
|
||||
class="group inline-flex items-center text-xs font-bold tracking-widest text-gray-500 uppercase transition-colors hover:text-brand-navy"
|
||||
class="group inline-flex items-center text-xs font-bold tracking-widest text-gray-500 uppercase transition-colors hover:text-brand-navy focus-visible:rounded-sm focus-visible:ring-2 focus-visible:ring-brand-navy focus-visible:outline-none"
|
||||
>
|
||||
<svg
|
||||
class="mr-2 h-4 w-4 transform transition-transform group-hover:-translate-x-1"
|
||||
|
||||
@@ -10,7 +10,7 @@ let { data }: { data: PageData } = $props();
|
||||
<div class="flex items-center gap-4">
|
||||
<a
|
||||
href="/admin/ocr"
|
||||
class="group inline-flex items-center text-xs font-bold tracking-widest text-gray-500 uppercase transition-colors hover:text-brand-navy"
|
||||
class="group inline-flex items-center text-xs font-bold tracking-widest text-gray-500 uppercase transition-colors hover:text-brand-navy focus-visible:rounded-sm focus-visible:ring-2 focus-visible:ring-brand-navy focus-visible:outline-none"
|
||||
>
|
||||
<svg
|
||||
class="mr-2 h-4 w-4 transform transition-transform group-hover:-translate-x-1"
|
||||
|
||||
Reference in New Issue
Block a user