Fix data exports UI and scope format
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Filament\Resources\DataExportResource\Tables;
|
||||
|
||||
use App\Enums\DataExportScope;
|
||||
use App\Jobs\GenerateDataExport;
|
||||
use App\Models\DataExport;
|
||||
use App\Services\Audit\SuperAdminAuditLogger;
|
||||
@@ -14,6 +15,15 @@ use Illuminate\Support\Number;
|
||||
|
||||
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
|
||||
{
|
||||
return $table
|
||||
@@ -31,7 +41,7 @@ class DataExportTable
|
||||
TextColumn::make('scope')
|
||||
->label(__('admin.data_exports.fields.scope'))
|
||||
->badge()
|
||||
->formatStateUsing(fn (?string $state) => $state ? __('admin.data_exports.scope.'.$state) : '—'),
|
||||
->formatStateUsing(fn (DataExportScope|string|null $state): string => self::formatScope($state)),
|
||||
TextColumn::make('status')
|
||||
->label(__('admin.data_exports.fields.status'))
|
||||
->badge()
|
||||
|
||||
@@ -23,6 +23,7 @@ import { formatRelativeTime } from './lib/relativeTime';
|
||||
import { useAdminTheme } from './theme';
|
||||
import i18n from '../i18n';
|
||||
import { triggerDownloadFromBlob } from './invite-layout/export-utils';
|
||||
import { hasInProgressExports } from './lib/dataExports';
|
||||
|
||||
const statusTone: Record<DataExportSummary['status'], 'success' | 'warning' | 'muted'> = {
|
||||
pending: 'warning',
|
||||
@@ -89,6 +90,22 @@ export default function MobileDataExportsPage() {
|
||||
void 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 () => {
|
||||
if (requesting) {
|
||||
return;
|
||||
@@ -152,7 +169,7 @@ export default function MobileDataExportsPage() {
|
||||
value={scope}
|
||||
onChange={(event) => setScope(event.target.value as 'tenant' | 'event')}
|
||||
compact
|
||||
style={{ minWidth: 160 }}
|
||||
style={{ minWidth: 140, maxWidth: 180 }}
|
||||
>
|
||||
<option value="tenant">{t('dataExports.scopes.tenant', 'Tenant')}</option>
|
||||
<option value="event">{t('dataExports.scopes.event', 'Event')}</option>
|
||||
@@ -167,7 +184,7 @@ export default function MobileDataExportsPage() {
|
||||
value={eventId ? String(eventId) : ''}
|
||||
onChange={(event) => setEventId(event.target.value ? Number(event.target.value) : null)}
|
||||
compact
|
||||
style={{ minWidth: 200 }}
|
||||
style={{ minWidth: 180, maxWidth: 220 }}
|
||||
>
|
||||
<option value="">{t('dataExports.fields.eventPlaceholder', 'Choose event')}</option>
|
||||
{events.map((event) => (
|
||||
@@ -187,7 +204,14 @@ export default function MobileDataExportsPage() {
|
||||
{t('dataExports.fields.includeMediaHint', 'Bigger ZIP; choose when needed.')}
|
||||
</Text>
|
||||
</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>
|
||||
</YStack>
|
||||
<CTAButton
|
||||
|
||||
22
resources/js/admin/mobile/lib/dataExports.test.ts
Normal file
22
resources/js/admin/mobile/lib/dataExports.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
5
resources/js/admin/mobile/lib/dataExports.ts
Normal file
5
resources/js/admin/mobile/lib/dataExports.ts
Normal 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');
|
||||
}
|
||||
31
tests/Unit/DataExportTableTest.php
Normal file
31
tests/Unit/DataExportTableTest.php
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user