Files
sbd2-frontend/src/pages/admin/SettingsPage.tsx
JSC 4a973e5044
Some checks failed
Frontend CI / lint (push) Failing after 18s
Frontend CI / build (push) Has been skipped
feat: add duplicates count to scan results and update success message in SettingsPage
2025-08-25 12:33:02 +02:00

301 lines
11 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { AppLayout } from '@/components/AppLayout'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Checkbox } from '@/components/ui/checkbox'
import { Label } from '@/components/ui/label'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import {
type NormalizationResponse,
type ScanResponse,
adminService,
} from '@/lib/api/services/admin'
import {
AudioWaveform,
FolderSync,
Loader2,
Scan,
Settings as SettingsIcon,
Volume2,
} from 'lucide-react'
import { useState } from 'react'
import { toast } from 'sonner'
export function SettingsPage() {
// Sound scanning state
const [scanningInProgress, setScanningInProgress] = useState(false)
const [lastScanResults, setLastScanResults] = useState<ScanResponse | null>(
null,
)
// Sound normalization state
const [normalizationInProgress, setNormalizationInProgress] = useState(false)
const [normalizationOptions, setNormalizationOptions] = useState({
force: false,
onePass: false,
soundType: 'all' as 'all' | 'SDB' | 'TTS' | 'EXT',
})
const [lastNormalizationResults, setLastNormalizationResults] =
useState<NormalizationResponse | null>(null)
const handleScanSounds = async () => {
setScanningInProgress(true)
try {
const response = await adminService.scanSounds()
setLastScanResults(response)
toast.success(
`Sound scan completed! Added: ${response.results.added}, Updated: ${response.results.updated}, Deleted: ${response.results.deleted}${response.results.duplicates > 0 ? `, Duplicates: ${response.results.duplicates}` : ''}`,
)
} catch (error) {
toast.error('Failed to scan sounds')
console.error('Sound scan error:', error)
} finally {
setScanningInProgress(false)
}
}
const handleNormalizeSounds = async () => {
setNormalizationInProgress(true)
try {
let response: NormalizationResponse
if (normalizationOptions.soundType === 'all') {
response = await adminService.normalizeAllSounds(
normalizationOptions.force,
normalizationOptions.onePass,
)
} else {
response = await adminService.normalizeSoundsByType(
normalizationOptions.soundType,
normalizationOptions.force,
normalizationOptions.onePass,
)
}
setLastNormalizationResults(response)
toast.success(
`Sound normalization completed! Processed: ${response.results.processed}, Normalized: ${response.results.normalized}`,
)
} catch (error) {
toast.error('Failed to normalize sounds')
console.error('Sound normalization error:', error)
} finally {
setNormalizationInProgress(false)
}
}
return (
<AppLayout
breadcrumb={{
items: [
{ label: 'Dashboard', href: '/' },
{ label: 'Admin' },
{ label: 'Settings' },
],
}}
>
<div className="flex-1 rounded-xl bg-muted/50 p-4">
<div className="flex items-center justify-between mb-6">
<div>
<h1 className="text-2xl font-bold">System Settings</h1>
<p className="text-muted-foreground">
Manage system-wide settings and operations
</p>
</div>
<SettingsIcon className="h-6 w-6 text-muted-foreground" />
</div>
<div className="grid gap-6 md:grid-cols-2">
{/* Sound Scanning */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<FolderSync className="h-5 w-5" />
Sound Scanning
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<p className="text-sm text-muted-foreground">
Scan the sound directories to synchronize new, updated, and
deleted audio files with the database.
</p>
<Button
onClick={handleScanSounds}
disabled={scanningInProgress}
className="w-full"
>
{scanningInProgress ? (
<>
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
Scanning...
</>
) : (
<>
<Scan className="h-4 w-4 mr-2" />
Scan Sound Directory
</>
)}
</Button>
{lastScanResults && (
<div className="bg-muted rounded-lg p-3 space-y-2">
<div className="text-sm font-medium">Last Scan Results:</div>
<div className="text-xs text-muted-foreground space-y-1">
<div> Added: {lastScanResults.results.added}</div>
<div>🔄 Updated: {lastScanResults.results.updated}</div>
<div>🗑 Deleted: {lastScanResults.results.deleted}</div>
<div> Skipped: {lastScanResults.results.skipped}</div>
{lastScanResults.results.duplicates > 0 && (
<div>📄 Duplicates: {lastScanResults.results.duplicates}</div>
)}
{lastScanResults.results.errors.length > 0 && (
<div>
Errors: {lastScanResults.results.errors.length}
</div>
)}
</div>
</div>
)}
</CardContent>
</Card>
{/* Sound Normalization */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<AudioWaveform className="h-5 w-5" />
Sound Normalization
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<p className="text-sm text-muted-foreground">
Normalize audio levels across all sounds using FFmpeg's loudnorm
filter for consistent volume.
</p>
<div className="space-y-3">
<div className="space-y-2">
<Label>Sound Type</Label>
<Select
value={normalizationOptions.soundType}
onValueChange={(value: 'all' | 'SDB' | 'TTS' | 'EXT') =>
setNormalizationOptions(prev => ({
...prev,
soundType: value,
}))
}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Sounds</SelectItem>
<SelectItem value="SDB">Soundboard (SDB)</SelectItem>
<SelectItem value="TTS">Text-to-Speech (TTS)</SelectItem>
<SelectItem value="EXT">Extracted (EXT)</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="force-normalize"
checked={normalizationOptions.force}
onCheckedChange={checked =>
setNormalizationOptions(prev => ({
...prev,
force: !!checked,
}))
}
/>
<Label htmlFor="force-normalize" className="text-sm">
Force re-normalization of already processed sounds
</Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="one-pass"
checked={normalizationOptions.onePass}
onCheckedChange={checked =>
setNormalizationOptions(prev => ({
...prev,
onePass: !!checked,
}))
}
/>
<Label htmlFor="one-pass" className="text-sm">
Use one-pass normalization (faster, lower quality)
</Label>
</div>
</div>
<Button
onClick={handleNormalizeSounds}
disabled={normalizationInProgress}
className="w-full"
>
{normalizationInProgress ? (
<>
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
Normalizing...
</>
) : (
<>
<Volume2 className="h-4 w-4 mr-2" />
Normalize Sounds
</>
)}
</Button>
{lastNormalizationResults && (
<div className="bg-muted rounded-lg p-3 space-y-2">
<div className="text-sm font-medium">
Last Normalization Results:
</div>
<div className="text-xs text-muted-foreground space-y-1">
<div>
🔄 Processed: {lastNormalizationResults.results.processed}
</div>
<div>
✅ Normalized:{' '}
{lastNormalizationResults.results.normalized}
</div>
<div>
Skipped: {lastNormalizationResults.results.skipped}
</div>
<div>
Errors: {lastNormalizationResults.results.errors}
</div>
{lastNormalizationResults.results.error_details.length >
0 && (
<details className="mt-2">
<summary className="cursor-pointer text-red-600">
View Error Details
</summary>
<div className="mt-1 text-xs text-red-600 space-y-1">
{lastNormalizationResults.results.error_details.map(
(error, index) => (
<div key={index}> {error}</div>
),
)}
</div>
</details>
)}
</div>
</div>
)}
</CardContent>
</Card>
</div>
</div>
</AppLayout>
)
}