style(badges): reports — consistent buttons, dark mode, 500-row cap
- Report selector now uses a tonal-primary container matching the pres_mgmt reports pattern, making Long Names / Print Throughput clearly clickable - All hardcoded gray-* colors replaced with surface-*-* tokens and dark: variants for proper light/dark mode support - Control buttons (field selector, threshold, window size) use btn-sm with visible borders matching the rest of the events module - Long Names table uses surface tokens on rows, header, borders - Print Throughput bar rows use surface tokens; expanded badge chips link to /print instead of /review - Long Names caps display at 500 rows with a warning note when hit Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,13 +6,12 @@ interface Props {
|
||||
}
|
||||
let { data, log_lvl = 0 }: Props = $props();
|
||||
|
||||
import { untrack } from 'svelte';
|
||||
import { liveQuery } from 'dexie';
|
||||
import { page } from '$app/state';
|
||||
|
||||
import { ae_loc } from '$lib/stores/ae_stores';
|
||||
import { db_events } from '$lib/ae_events/db_events';
|
||||
import { events_loc, events_slct } from '$lib/stores/ae_events_stores';
|
||||
import { events_loc } from '$lib/stores/ae_events_stores';
|
||||
|
||||
import { ArrowLeft, TrendingUp, Type, Gauge, LoaderCircle } from '@lucide/svelte';
|
||||
import Reports_badge_long_names from './reports_badge_long_names.svelte';
|
||||
@@ -56,46 +55,55 @@ let reprint_count = $derived(
|
||||
<p class="text-error-500 font-semibold">Trusted access required for badge reports.</p>
|
||||
</div>
|
||||
{:else}
|
||||
<header class="mb-4 flex w-full flex-row flex-wrap items-center justify-between gap-2 border-b border-gray-300 pb-2">
|
||||
<header class="mb-3 flex w-full flex-row flex-wrap items-center justify-between gap-2 border-b border-surface-300 dark:border-surface-700 pb-2">
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<a
|
||||
href={`/events/${event_id}/badges`}
|
||||
class="btn btn-sm preset-tonal-surface flex items-center gap-1">
|
||||
class="btn btn-sm preset-tonal-surface border border-surface-300-700 flex items-center gap-1">
|
||||
<ArrowLeft size="1em" />
|
||||
<span class="hidden sm:inline">Badges</span>
|
||||
</a>
|
||||
<div class="flex flex-col">
|
||||
<h2 class="flex items-center gap-1 text-base font-bold">
|
||||
<TrendingUp size="1em" class="shrink-0" />
|
||||
<TrendingUp size="1em" class="shrink-0 text-primary-500" />
|
||||
Badge Reports
|
||||
</h2>
|
||||
{#if $lq__event_obj?.name}
|
||||
<p class="text-sm text-gray-500">{$lq__event_obj.name}</p>
|
||||
<p class="text-sm text-surface-600-400">{$lq__event_obj.name}</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-sm text-gray-500">
|
||||
<div class="text-sm text-surface-600-400">
|
||||
{badge_count} badges · {printed_count} printed{reprint_count > 0 ? ` (${reprint_count} reprint${reprint_count !== 1 ? 's' : ''})` : ''}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Report selector -->
|
||||
<div class="mb-4 flex flex-row flex-wrap gap-2">
|
||||
<!-- Report selector — styled like pres_mgmt reports for visual consistency -->
|
||||
<div class="preset-tonal-primary border-primary-300 dark:border-primary-700 my-2 flex flex-row flex-wrap items-center gap-2 rounded-md border p-2">
|
||||
<span class="text-xs font-semibold uppercase tracking-wide text-primary-700 dark:text-primary-300 pr-1">
|
||||
Reports:
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (active_report = 'long_names')}
|
||||
class="btn btn-sm flex items-center gap-1 transition-all"
|
||||
class="btn btn-sm border transition-all"
|
||||
class:preset-filled-primary={active_report === 'long_names'}
|
||||
class:preset-tonal-surface={active_report !== 'long_names'}>
|
||||
class:border-primary-600={active_report === 'long_names'}
|
||||
class:preset-tonal-primary={active_report !== 'long_names'}
|
||||
class:border-primary-400={active_report !== 'long_names'}
|
||||
title="List badges with the longest given, family, or full names">
|
||||
<Type size="1em" />
|
||||
Long Names
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (active_report = 'print_throughput')}
|
||||
class="btn btn-sm flex items-center gap-1 transition-all"
|
||||
class="btn btn-sm border transition-all"
|
||||
class:preset-filled-primary={active_report === 'print_throughput'}
|
||||
class:preset-tonal-surface={active_report !== 'print_throughput'}>
|
||||
class:border-primary-600={active_report === 'print_throughput'}
|
||||
class:preset-tonal-primary={active_report !== 'print_throughput'}
|
||||
class:border-primary-400={active_report !== 'print_throughput'}
|
||||
title="Show number of badges printed per time window">
|
||||
<Gauge size="1em" />
|
||||
Print Throughput
|
||||
</button>
|
||||
@@ -111,6 +119,6 @@ let reprint_count = $derived(
|
||||
{:else if active_report === 'print_throughput'}
|
||||
<Reports_badge_print_throughput badge_li={$lq__badge_li} event_id={event_id ?? ''} {log_lvl} />
|
||||
{:else}
|
||||
<div class="text-gray-500 py-4 text-sm">Select a report above to get started.</div>
|
||||
<p class="py-4 text-sm text-surface-600-400">Select a report above to view results.</p>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
@@ -12,6 +12,8 @@ type NameField = 'given' | 'family' | 'full';
|
||||
let name_field: NameField = $state('full');
|
||||
let threshold: number = $state(20);
|
||||
|
||||
const MAX_DISPLAY = 500;
|
||||
|
||||
function get_display_name(badge: any): string {
|
||||
switch (name_field) {
|
||||
case 'given':
|
||||
@@ -29,7 +31,7 @@ function get_display_name(badge: any): string {
|
||||
}
|
||||
|
||||
function get_field_label(field: NameField): string {
|
||||
return field === 'given' ? 'Given Name' : field === 'family' ? 'Family Name' : 'Full Name';
|
||||
return field === 'given' ? 'Given' : field === 'family' ? 'Family' : 'Full Name';
|
||||
}
|
||||
|
||||
interface BadgeRow {
|
||||
@@ -41,79 +43,83 @@ interface BadgeRow {
|
||||
has_override: boolean;
|
||||
}
|
||||
|
||||
let results: BadgeRow[] = $derived.by(() => {
|
||||
let all_results: BadgeRow[] = $derived.by(() => {
|
||||
if (!badge_li?.length) return [];
|
||||
return badge_li
|
||||
.map((b) => {
|
||||
const display_name = get_display_name(b);
|
||||
const has_override =
|
||||
name_field === 'full'
|
||||
? !!b.full_name_override
|
||||
: false;
|
||||
return {
|
||||
event_badge_id: b.event_badge_id,
|
||||
display_name,
|
||||
name_len: display_name.length,
|
||||
badge_type: b.badge_type_override ?? b.badge_type ?? null,
|
||||
affiliations: b.affiliations_override ?? b.affiliations ?? null,
|
||||
has_override
|
||||
has_override: name_field === 'full' ? !!b.full_name_override : false
|
||||
};
|
||||
})
|
||||
.filter((r) => r.name_len >= threshold)
|
||||
.sort((a, b) => b.name_len - a.name_len);
|
||||
});
|
||||
|
||||
let results = $derived(all_results.slice(0, MAX_DISPLAY));
|
||||
let is_truncated = $derived(all_results.length > MAX_DISPLAY);
|
||||
</script>
|
||||
|
||||
<div class="space-y-3">
|
||||
<!-- Controls -->
|
||||
<div class="flex flex-row flex-wrap items-center gap-3">
|
||||
<!-- Field selector -->
|
||||
<div class="flex flex-row items-center gap-1 text-sm">
|
||||
<span class="text-gray-500">Field:</span>
|
||||
<div class="flex flex-row items-center gap-1">
|
||||
<span class="text-sm text-surface-600-400">Field:</span>
|
||||
{#each (['full', 'given', 'family'] as NameField[]) as field}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (name_field = field)}
|
||||
class="btn btn-xs transition-all"
|
||||
class="btn btn-sm border transition-all"
|
||||
class:preset-filled-primary={name_field === field}
|
||||
class:preset-tonal-surface={name_field !== field}>
|
||||
class:border-primary-600={name_field === field}
|
||||
class:preset-tonal-surface={name_field !== field}
|
||||
class:border-surface-300-700={name_field !== field}>
|
||||
{get_field_label(field)}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<!-- Threshold stepper -->
|
||||
<div class="flex flex-row items-center gap-1 text-sm">
|
||||
<span class="text-gray-500">Min length:</span>
|
||||
<div class="flex flex-row items-center gap-1">
|
||||
<span class="text-sm text-surface-600-400">Min length:</span>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (threshold = Math.max(5, threshold - 1))}
|
||||
class="btn btn-xs preset-tonal-surface w-7"
|
||||
class="btn btn-sm preset-tonal-surface border border-surface-300-700 w-8"
|
||||
aria-label="Decrease threshold">−</button>
|
||||
<span class="w-6 text-center font-mono font-semibold">{threshold}</span>
|
||||
<span class="w-7 text-center font-mono font-semibold">{threshold}</span>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (threshold = Math.min(60, threshold + 1))}
|
||||
class="btn btn-xs preset-tonal-surface w-7"
|
||||
class="btn btn-sm preset-tonal-surface border border-surface-300-700 w-8"
|
||||
aria-label="Increase threshold">+</button>
|
||||
</div>
|
||||
|
||||
<!-- Result count -->
|
||||
<span class="text-sm text-gray-500">
|
||||
{results.length} badge{results.length !== 1 ? 's' : ''} with {get_field_label(name_field).toLowerCase()} ≥ {threshold} chars
|
||||
<span class="text-sm text-surface-600-400">
|
||||
{all_results.length} badge{all_results.length !== 1 ? 's' : ''} with {get_field_label(name_field).toLowerCase()} ≥ {threshold} chars
|
||||
{#if is_truncated}
|
||||
<span class="text-warning-600 dark:text-warning-400">(showing first {MAX_DISPLAY})</span>
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Results table -->
|
||||
{#if results.length === 0}
|
||||
<p class="py-4 text-sm text-gray-500">
|
||||
<p class="py-4 text-sm text-surface-600-400">
|
||||
No badges found with a {get_field_label(name_field).toLowerCase()} of {threshold} or more characters.
|
||||
</p>
|
||||
{:else}
|
||||
<div class="overflow-x-auto rounded-md border border-gray-200">
|
||||
<div class="overflow-x-auto rounded-md border border-surface-200 dark:border-surface-700">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="border-b border-gray-200 bg-gray-50 text-left text-xs uppercase tracking-wide text-gray-500">
|
||||
<tr class="border-b border-surface-200 dark:border-surface-700 bg-surface-100 dark:bg-surface-800 text-left text-xs uppercase tracking-wide text-surface-600-400">
|
||||
<th class="px-3 py-2">Name ({get_field_label(name_field)})</th>
|
||||
<th class="px-3 py-2 text-right">Len</th>
|
||||
<th class="px-3 py-2">Badge Type</th>
|
||||
@@ -123,32 +129,36 @@ let results: BadgeRow[] = $derived.by(() => {
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each results as row (row.event_badge_id)}
|
||||
<tr class="border-b border-gray-100 hover:bg-gray-50">
|
||||
<tr class="border-b border-surface-100 dark:border-surface-800 hover:bg-surface-50 dark:hover:bg-surface-900/50 transition-colors">
|
||||
<td class="px-3 py-2 font-medium">
|
||||
{row.display_name}
|
||||
{#if row.has_override}
|
||||
<span class="ml-1 rounded bg-amber-100 px-1 text-xs text-amber-700" title="Override value">override</span>
|
||||
<span class="ml-1 rounded bg-warning-100 dark:bg-warning-900/30 px-1 text-xs text-warning-700 dark:text-warning-300" title="Staff override value">override</span>
|
||||
{/if}
|
||||
</td>
|
||||
<td class="px-3 py-2 text-right font-mono">
|
||||
<span
|
||||
class="rounded px-1 font-semibold"
|
||||
class:text-red-700={row.name_len >= 30}
|
||||
class:bg-red-100={row.name_len >= 30}
|
||||
class:text-amber-700={row.name_len >= 25 && row.name_len < 30}
|
||||
class:bg-amber-100={row.name_len >= 25 && row.name_len < 30}
|
||||
class:text-gray-600={row.name_len < 25}>
|
||||
class:text-error-600={row.name_len >= 30}
|
||||
class:dark:text-error-400={row.name_len >= 30}
|
||||
class:bg-error-100={row.name_len >= 30}
|
||||
class:dark:bg-error-900={row.name_len >= 30}
|
||||
class:text-warning-700={row.name_len >= 25 && row.name_len < 30}
|
||||
class:dark:text-warning-400={row.name_len >= 25 && row.name_len < 30}
|
||||
class:bg-warning-100={row.name_len >= 25 && row.name_len < 30}
|
||||
class:dark:bg-warning-900={row.name_len >= 25 && row.name_len < 30}
|
||||
class:text-surface-600-400={row.name_len < 25}>
|
||||
{row.name_len}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-3 py-2 text-gray-600">{row.badge_type ?? '—'}</td>
|
||||
<td class="max-w-48 truncate px-3 py-2 text-gray-600" title={row.affiliations ?? ''}>
|
||||
<td class="px-3 py-2 text-surface-600-400">{row.badge_type ?? '—'}</td>
|
||||
<td class="max-w-48 truncate px-3 py-2 text-surface-600-400" title={row.affiliations ?? ''}>
|
||||
{row.affiliations ?? '—'}
|
||||
</td>
|
||||
<td class="px-3 py-2">
|
||||
<a
|
||||
href={`/events/${event_id}/badges/${row.event_badge_id}/print`}
|
||||
class="btn btn-xs preset-tonal-primary flex items-center gap-1"
|
||||
class="btn btn-sm preset-tonal-primary border border-primary-300-700 flex items-center gap-1"
|
||||
title={`Edit badge · ${row.event_badge_id} · ${row.display_name}`}>
|
||||
<UserRoundPen size="0.9em" />
|
||||
Edit
|
||||
|
||||
@@ -13,7 +13,7 @@ let expanded_bucket: number | null = $state(null);
|
||||
interface Bucket {
|
||||
start_ms: number;
|
||||
label: string;
|
||||
date_label: string | null; // shown when day changes
|
||||
date_label: string | null;
|
||||
count: number;
|
||||
badges: any[];
|
||||
}
|
||||
@@ -96,22 +96,24 @@ function get_effective_name(badge: any): string {
|
||||
<div class="space-y-3">
|
||||
<!-- Controls -->
|
||||
<div class="flex flex-row flex-wrap items-center gap-3">
|
||||
<div class="flex flex-row items-center gap-1 text-sm">
|
||||
<span class="text-gray-500">Window:</span>
|
||||
<div class="flex flex-row items-center gap-1">
|
||||
<span class="text-sm text-surface-600-400">Window:</span>
|
||||
{#each ([5, 15, 30, 60] as BucketMin[]) as sz}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => { bucket_size = sz; expanded_bucket = null; }}
|
||||
class="btn btn-xs transition-all"
|
||||
class="btn btn-sm border transition-all"
|
||||
class:preset-filled-primary={bucket_size === sz}
|
||||
class:preset-tonal-surface={bucket_size !== sz}>
|
||||
class:border-primary-600={bucket_size === sz}
|
||||
class:preset-tonal-surface={bucket_size !== sz}
|
||||
class:border-surface-300-700={bucket_size !== sz}>
|
||||
{sz} min
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
{#if stats.total_printed > 0}
|
||||
<span class="text-sm text-gray-500">
|
||||
<span class="text-sm text-surface-600-400">
|
||||
{stats.total_printed} printed · {stats.buckets.length} window{stats.buckets.length !== 1 ? 's' : ''} with activity
|
||||
{#if stats.span_label}· {stats.span_label}{/if}
|
||||
</span>
|
||||
@@ -120,68 +122,72 @@ function get_effective_name(badge: any): string {
|
||||
|
||||
<!-- Chart / table -->
|
||||
{#if stats.total_printed === 0}
|
||||
<p class="py-4 text-sm text-gray-500">No printed badges found for this event.</p>
|
||||
<p class="py-4 text-sm text-surface-600-400">No printed badges found for this event.</p>
|
||||
{:else if stats.buckets.length === 0}
|
||||
<p class="py-4 text-sm text-gray-500">No valid print timestamps found.</p>
|
||||
<p class="py-4 text-sm text-surface-600-400">No valid print timestamps found.</p>
|
||||
{:else}
|
||||
<div class="space-y-0.5">
|
||||
{#each stats.buckets as bucket (bucket.start_ms)}
|
||||
<!-- Date separator when the day changes -->
|
||||
{#if bucket.date_label}
|
||||
<div class="pt-2 pb-1 text-xs font-semibold uppercase tracking-wide text-gray-400">
|
||||
<div class="pt-2 pb-1 text-xs font-semibold uppercase tracking-wide text-surface-500-400">
|
||||
{bucket.date_label}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Bar row -->
|
||||
<div class="group">
|
||||
<div class="group rounded border border-transparent hover:border-surface-200 dark:hover:border-surface-700 transition-colors">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (expanded_bucket = expanded_bucket === bucket.start_ms ? null : bucket.start_ms)}
|
||||
class="flex w-full flex-row items-center gap-2 rounded px-2 py-1 text-left hover:bg-gray-50 transition-colors">
|
||||
class="flex w-full flex-row items-center gap-2 rounded px-2 py-1.5 text-left hover:bg-surface-50 dark:hover:bg-surface-900/50 transition-colors">
|
||||
<!-- Time label -->
|
||||
<span class="w-14 shrink-0 font-mono text-sm text-gray-600">
|
||||
<span class="w-14 shrink-0 font-mono text-sm">
|
||||
{bucket.label}
|
||||
</span>
|
||||
|
||||
<!-- Bar -->
|
||||
<div class="flex-1 overflow-hidden rounded-sm bg-gray-100 h-5">
|
||||
<div class="flex-1 overflow-hidden rounded-sm bg-surface-200 dark:bg-surface-700 h-5">
|
||||
<div
|
||||
class="h-full rounded-sm bg-primary-400 transition-all duration-300"
|
||||
class="h-full rounded-sm transition-all duration-300"
|
||||
class:bg-primary-500={bucket.count < stats.max_count}
|
||||
class:bg-primary-400={bucket.count === stats.max_count && stats.max_count > 1}
|
||||
class:dark:bg-primary-400={bucket.count < stats.max_count}
|
||||
class:dark:bg-primary-300={bucket.count === stats.max_count && stats.max_count > 1}
|
||||
style:width="{Math.round((bucket.count / stats.max_count) * 100)}%">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Count badge -->
|
||||
<!-- Count -->
|
||||
<span
|
||||
class="w-8 shrink-0 text-right text-sm font-semibold"
|
||||
class:text-primary-600={bucket.count === stats.max_count}
|
||||
class:text-gray-700={bucket.count !== stats.max_count}>
|
||||
class:text-primary-600={bucket.count === stats.max_count && stats.max_count > 1}
|
||||
class:dark:text-primary-400={bucket.count === stats.max_count && stats.max_count > 1}>
|
||||
{bucket.count}
|
||||
</span>
|
||||
|
||||
<!-- Peak indicator -->
|
||||
{#if bucket.count === stats.max_count && stats.max_count > 1}
|
||||
<span class="shrink-0 rounded bg-primary-100 px-1 text-xs text-primary-700">peak</span>
|
||||
{:else}
|
||||
<span class="w-10 shrink-0"></span>
|
||||
{/if}
|
||||
<!-- Peak chip -->
|
||||
<span class="w-10 shrink-0 text-right">
|
||||
{#if bucket.count === stats.max_count && stats.max_count > 1}
|
||||
<span class="rounded bg-primary-100 dark:bg-primary-900/40 px-1 text-xs text-primary-700 dark:text-primary-300">peak</span>
|
||||
{/if}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<!-- Expanded badge list -->
|
||||
{#if expanded_bucket === bucket.start_ms}
|
||||
<div class="mx-2 mb-1 rounded border border-gray-100 bg-gray-50 px-3 py-2">
|
||||
<div class="mx-2 mb-2 rounded border border-surface-200 dark:border-surface-700 bg-surface-50 dark:bg-surface-900/30 px-3 py-2">
|
||||
<div class="flex flex-row flex-wrap gap-1">
|
||||
{#each bucket.badges as b (b.event_badge_id)}
|
||||
<a
|
||||
href={`/events/${event_id}/badges/${b.event_badge_id}/review`}
|
||||
class="rounded border border-gray-200 bg-white px-2 py-0.5 text-xs hover:bg-primary-50 hover:border-primary-200 transition-colors">
|
||||
href={`/events/${event_id}/badges/${b.event_badge_id}/print`}
|
||||
class="rounded border border-surface-200 dark:border-surface-700 bg-white dark:bg-surface-800 px-2 py-0.5 text-xs hover:border-primary-300 dark:hover:border-primary-600 hover:bg-primary-50 dark:hover:bg-primary-900/20 transition-colors">
|
||||
{get_effective_name(b)}
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
<p class="mt-1 text-right text-xs text-gray-400">
|
||||
{bucket.count} badge{bucket.count !== 1 ? 's' : ''} printed in this {bucket_size}-min window · click badge to review
|
||||
<p class="mt-1.5 text-right text-xs text-surface-500-400">
|
||||
{bucket.count} badge{bucket.count !== 1 ? 's' : ''} printed in this {bucket_size}-min window · click to open
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -190,16 +196,10 @@ function get_effective_name(badge: any): string {
|
||||
</div>
|
||||
|
||||
<!-- Summary row -->
|
||||
<div class="mt-3 flex flex-row flex-wrap gap-4 border-t border-gray-200 pt-3 text-sm">
|
||||
<span class="text-gray-500">
|
||||
Total printed: <span class="font-semibold text-gray-800">{stats.total_printed}</span>
|
||||
</span>
|
||||
<span class="text-gray-500">
|
||||
Peak window: <span class="font-semibold text-gray-800">{stats.max_count}</span> in {bucket_size} min
|
||||
</span>
|
||||
<span class="text-gray-500">
|
||||
Avg per window: <span class="font-semibold text-gray-800">{stats.buckets.length ? (stats.total_printed / stats.buckets.length).toFixed(1) : '—'}</span>
|
||||
</span>
|
||||
<div class="mt-3 flex flex-row flex-wrap gap-4 border-t border-surface-200 dark:border-surface-700 pt-3 text-sm text-surface-600-400">
|
||||
<span>Total printed: <span class="font-semibold text-inherit">{stats.total_printed}</span></span>
|
||||
<span>Peak window: <span class="font-semibold text-inherit">{stats.max_count}</span> in {bucket_size} min</span>
|
||||
<span>Avg per window: <span class="font-semibold text-inherit">{stats.buckets.length ? (stats.total_printed / stats.buckets.length).toFixed(1) : '—'}</span></span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user