Fix data exports UI and scope format
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled

This commit is contained in:
Codex Agent
2026-01-06 12:59:38 +01:00
parent e82a10cb8b
commit a3538f6470
5 changed files with 96 additions and 4 deletions

View File

@@ -2,6 +2,7 @@
namespace App\Filament\Resources\DataExportResource\Tables; namespace App\Filament\Resources\DataExportResource\Tables;
use App\Enums\DataExportScope;
use App\Jobs\GenerateDataExport; use App\Jobs\GenerateDataExport;
use App\Models\DataExport; use App\Models\DataExport;
use App\Services\Audit\SuperAdminAuditLogger; use App\Services\Audit\SuperAdminAuditLogger;
@@ -14,6 +15,15 @@ use Illuminate\Support\Number;
class DataExportTable class DataExportTable
{ {
public static function formatScope(DataExportScope|string|null $state): string
{
if ($state instanceof DataExportScope) {
$state = $state->value;
}
return $state ? __('admin.data_exports.scope.'.$state) : '—';
}
public static function configure(Table $table): Table public static function configure(Table $table): Table
{ {
return $table return $table
@@ -31,7 +41,7 @@ class DataExportTable
TextColumn::make('scope') TextColumn::make('scope')
->label(__('admin.data_exports.fields.scope')) ->label(__('admin.data_exports.fields.scope'))
->badge() ->badge()
->formatStateUsing(fn (?string $state) => $state ? __('admin.data_exports.scope.'.$state) : '—'), ->formatStateUsing(fn (DataExportScope|string|null $state): string => self::formatScope($state)),
TextColumn::make('status') TextColumn::make('status')
->label(__('admin.data_exports.fields.status')) ->label(__('admin.data_exports.fields.status'))
->badge() ->badge()

View File

@@ -23,6 +23,7 @@ import { formatRelativeTime } from './lib/relativeTime';
import { useAdminTheme } from './theme'; import { useAdminTheme } from './theme';
import i18n from '../i18n'; import i18n from '../i18n';
import { triggerDownloadFromBlob } from './invite-layout/export-utils'; import { triggerDownloadFromBlob } from './invite-layout/export-utils';
import { hasInProgressExports } from './lib/dataExports';
const statusTone: Record<DataExportSummary['status'], 'success' | 'warning' | 'muted'> = { const statusTone: Record<DataExportSummary['status'], 'success' | 'warning' | 'muted'> = {
pending: 'warning', pending: 'warning',
@@ -89,6 +90,22 @@ export default function MobileDataExportsPage() {
void load(); void load();
}, [load]); }, [load]);
const hasInProgress = hasInProgressExports(exports);
React.useEffect(() => {
if (!hasInProgress) {
return;
}
const interval = window.setInterval(() => {
void load();
}, 5000);
return () => {
window.clearInterval(interval);
};
}, [hasInProgress, load]);
const handleRequest = async () => { const handleRequest = async () => {
if (requesting) { if (requesting) {
return; return;
@@ -152,7 +169,7 @@ export default function MobileDataExportsPage() {
value={scope} value={scope}
onChange={(event) => setScope(event.target.value as 'tenant' | 'event')} onChange={(event) => setScope(event.target.value as 'tenant' | 'event')}
compact compact
style={{ minWidth: 160 }} style={{ minWidth: 140, maxWidth: 180 }}
> >
<option value="tenant">{t('dataExports.scopes.tenant', 'Tenant')}</option> <option value="tenant">{t('dataExports.scopes.tenant', 'Tenant')}</option>
<option value="event">{t('dataExports.scopes.event', 'Event')}</option> <option value="event">{t('dataExports.scopes.event', 'Event')}</option>
@@ -167,7 +184,7 @@ export default function MobileDataExportsPage() {
value={eventId ? String(eventId) : ''} value={eventId ? String(eventId) : ''}
onChange={(event) => setEventId(event.target.value ? Number(event.target.value) : null)} onChange={(event) => setEventId(event.target.value ? Number(event.target.value) : null)}
compact compact
style={{ minWidth: 200 }} style={{ minWidth: 180, maxWidth: 220 }}
> >
<option value="">{t('dataExports.fields.eventPlaceholder', 'Choose event')}</option> <option value="">{t('dataExports.fields.eventPlaceholder', 'Choose event')}</option>
{events.map((event) => ( {events.map((event) => (
@@ -187,7 +204,14 @@ export default function MobileDataExportsPage() {
{t('dataExports.fields.includeMediaHint', 'Bigger ZIP; choose when needed.')} {t('dataExports.fields.includeMediaHint', 'Bigger ZIP; choose when needed.')}
</Text> </Text>
</YStack> </YStack>
<Switch checked={includeMedia} onCheckedChange={setIncludeMedia} /> <Switch
size="$3"
checked={includeMedia}
onCheckedChange={setIncludeMedia}
aria-label={t('dataExports.fields.includeMedia', 'Include raw media')}
>
<Switch.Thumb />
</Switch>
</XStack> </XStack>
</YStack> </YStack>
<CTAButton <CTAButton

View File

@@ -0,0 +1,22 @@
import { describe, expect, it } from 'vitest';
import { hasInProgressExports } from './dataExports';
describe('dataExports helpers', () => {
it('detects in-progress exports', () => {
const result = hasInProgressExports([
{ id: 1, status: 'pending' } as any,
{ id: 2, status: 'ready' } as any,
]);
expect(result).toBe(true);
});
it('returns false when exports are terminal', () => {
const result = hasInProgressExports([
{ id: 1, status: 'ready' } as any,
{ id: 2, status: 'failed' } as any,
]);
expect(result).toBe(false);
});
});

View File

@@ -0,0 +1,5 @@
import type { DataExportSummary } from '../../api';
export function hasInProgressExports(exports: DataExportSummary[]): boolean {
return exports.some((entry) => entry.status === 'pending' || entry.status === 'processing');
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Tests\Unit;
use App\Enums\DataExportScope;
use App\Filament\Resources\DataExportResource\Tables\DataExportTable;
use Tests\TestCase;
class DataExportTableTest extends TestCase
{
public function test_formats_scope_from_enum(): void
{
$label = DataExportTable::formatScope(DataExportScope::TENANT);
$this->assertSame(__('admin.data_exports.scope.tenant'), $label);
}
public function test_formats_scope_from_string(): void
{
$label = DataExportTable::formatScope('event');
$this->assertSame(__('admin.data_exports.scope.event'), $label);
}
public function test_formats_scope_from_null(): void
{
$label = DataExportTable::formatScope(null);
$this->assertSame('—', $label);
}
}