feat: implement character changes page with new and modified character listings
All checks were successful
Build Docker Image / build (push) Successful in 1m23s
All checks were successful
Build Docker Image / build (push) Successful in 1m23s
This commit is contained in:
146
src/routes/(admin)/admin/character-changes/+page.svelte
Normal file
146
src/routes/(admin)/admin/character-changes/+page.svelte
Normal file
@@ -0,0 +1,146 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
const newCharacters = $derived(data.changes.filter((c: any) => c.type === 'new'));
|
||||
const modifiedCharacters = $derived(data.changes.filter((c: any) => c.type === 'modified'));
|
||||
|
||||
function formatValue(value: any): string {
|
||||
if (value === null || value === undefined) {
|
||||
return '—';
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
return value.join(', ');
|
||||
}
|
||||
if (typeof value === 'boolean') {
|
||||
return value ? '✓' : '✗';
|
||||
}
|
||||
return String(value);
|
||||
}
|
||||
|
||||
function getDifferenceColor(current: any, scraped: any): string {
|
||||
if (JSON.stringify(current) === JSON.stringify(scraped)) {
|
||||
return 'text-gray-400';
|
||||
}
|
||||
return 'text-amber-300';
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Admin - Character Changes</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="space-y-8">
|
||||
<div>
|
||||
<h1 class="text-3xl font-black uppercase tracking-[0.25em] text-amber-50 mb-2">Character Changes</h1>
|
||||
<p class="text-gray-400">Total changes: {newCharacters.length} new, {modifiedCharacters.length} modified</p>
|
||||
</div>
|
||||
|
||||
<!-- New Characters Section -->
|
||||
{#if newCharacters.length > 0}
|
||||
<section class="space-y-4">
|
||||
<h2 class="text-xl font-bold text-emerald-400 uppercase tracking-[0.15em]">
|
||||
🆕 New Characters ({newCharacters.length})
|
||||
</h2>
|
||||
<div class="grid gap-4">
|
||||
{#each newCharacters as change (change.id)}
|
||||
<div class="rounded-lg border border-emerald-500/30 bg-emerald-500/5 p-4 space-y-3">
|
||||
<div class="flex items-center gap-3">
|
||||
{#if change.scraped.pictureUrl}
|
||||
<img
|
||||
src={change.scraped.pictureUrl}
|
||||
alt={change.scraped.name}
|
||||
class="w-12 h-12 rounded object-cover"
|
||||
/>
|
||||
{/if}
|
||||
<div>
|
||||
<h3 class="font-bold text-emerald-300">{change.scraped.name}</h3>
|
||||
<p class="text-sm text-gray-500">{change.id}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-2 text-sm">
|
||||
<div>
|
||||
<span class="text-gray-400">Status:</span>
|
||||
<span class="ml-2">{formatValue(change.scraped.status)}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-gray-400">Gender:</span>
|
||||
<span class="ml-2">{formatValue(change.scraped.gender)}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-gray-400">Age:</span>
|
||||
<span class="ml-2">{formatValue(change.scraped.age)}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-gray-400">Bounty:</span>
|
||||
<span class="ml-2">{formatValue(change.scraped.bounty)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<!-- Modified Characters Section -->
|
||||
{#if modifiedCharacters.length > 0}
|
||||
<section class="space-y-4">
|
||||
<h2 class="text-xl font-bold text-amber-400 uppercase tracking-[0.15em]">
|
||||
✏️ Modified Characters ({modifiedCharacters.length})
|
||||
</h2>
|
||||
<div class="grid gap-6">
|
||||
{#each modifiedCharacters as change (change.id)}
|
||||
<div class="rounded-lg border border-amber-500/30 bg-amber-500/5 p-4 space-y-4">
|
||||
<div class="flex items-center gap-3 pb-4 border-b border-amber-500/20">
|
||||
{#if change.current?.pictureUrl}
|
||||
<img
|
||||
src={change.current.pictureUrl}
|
||||
alt={change.current.name}
|
||||
class="w-12 h-12 rounded object-cover"
|
||||
/>
|
||||
{/if}
|
||||
<div>
|
||||
<h3 class="font-bold text-amber-300">{change.current?.name ?? change.scraped.name}</h3>
|
||||
<p class="text-sm text-gray-500">{change.id}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if change.differences}
|
||||
<div class="space-y-3">
|
||||
{#each Object.entries(change.differences) as [field, diff]}
|
||||
<div class="bg-slate-900/50 rounded p-3 space-y-1">
|
||||
<h4 class="text-sm font-semibold text-amber-100 uppercase tracking-widest">{field}</h4>
|
||||
<div class="grid grid-cols-2 gap-4 text-sm">
|
||||
<div class="space-y-1">
|
||||
<p class="text-gray-500">Current:</p>
|
||||
<p class="font-mono text-gray-300">{formatValue(diff.current)}</p>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<p class="text-gray-500">Scraped:</p>
|
||||
<p class="font-mono text-emerald-300 font-semibold">{formatValue(diff.scraped)}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
{#if newCharacters.length === 0 && modifiedCharacters.length === 0}
|
||||
<div class="rounded-lg border border-white/10 bg-white/5 p-8 text-center">
|
||||
<p class="text-gray-400">Aucun changement détecté. Les tables character et characterScrapeValidation sont synchronisées.</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
:global(body) {
|
||||
background-color: rgb(15, 23, 42);
|
||||
color: rgb(203, 213, 225);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user