feat: Implement Phase 5 match detail tabs with charts and data visualization
This commit implements significant portions of Phase 5 (Feature Delivery) including: Chart Components (src/lib/components/charts/): - LineChart.svelte: Line charts with Chart.js integration - BarChart.svelte: Vertical/horizontal bar charts with stacking - PieChart.svelte: Pie/Doughnut charts with legend - All charts use Svelte 5 runes ($effect) for reactivity - Responsive design with customizable options Data Display Components (src/lib/components/data-display/): - DataTable.svelte: Generic sortable, filterable table component - TypeScript generics support for type safety - Custom formatters and renderers - Sort indicators and column alignment options Match Detail Pages: - Match layout with header, tabs, and score display - Economy tab: Equipment value charts, buy type classification, round-by-round table - Details tab: Multi-kill distribution charts, team performance, top performers - Chat tab: Chronological messages with filtering, search, and round grouping Additional Components: - SearchBar, ThemeToggle (layout components) - MatchCard, PlayerCard (domain components) - Modal, Skeleton, Tabs, Tooltip (UI components) - Player profile page with stats and recent matches Dependencies: - Installed chart.js for data visualization - Created Svelte 5 compatible chart wrappers Phase 4 marked as complete, Phase 5 at 50% completion. Flashes and Damage tabs deferred for future implementation. Note: Minor linting warnings to be addressed in follow-up commit. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
116
src/lib/components/layout/SearchBar.svelte
Normal file
116
src/lib/components/layout/SearchBar.svelte
Normal file
@@ -0,0 +1,116 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { Search, Command } from 'lucide-svelte';
|
||||
import { search } from '$lib/stores';
|
||||
import Modal from '$lib/components/ui/Modal.svelte';
|
||||
|
||||
let open = $state(false);
|
||||
let query = $state('');
|
||||
let searchInput: HTMLInputElement;
|
||||
|
||||
// Keyboard shortcut: Cmd/Ctrl + K
|
||||
const handleKeydown = (e: KeyboardEvent) => {
|
||||
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
||||
e.preventDefault();
|
||||
open = true;
|
||||
setTimeout(() => searchInput?.focus(), 100);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSearch = (e: Event) => {
|
||||
e.preventDefault();
|
||||
if (!query.trim()) return;
|
||||
|
||||
// Add to recent searches
|
||||
search.addRecentSearch(query);
|
||||
|
||||
// Navigate to matches page with search query
|
||||
goto(`/matches?search=${encodeURIComponent(query)}`);
|
||||
|
||||
// Close modal and clear
|
||||
open = false;
|
||||
query = '';
|
||||
};
|
||||
|
||||
const handleRecentClick = (recentQuery: string) => {
|
||||
query = recentQuery;
|
||||
handleSearch(new Event('submit'));
|
||||
};
|
||||
|
||||
const handleClearRecent = () => {
|
||||
search.clearRecentSearches();
|
||||
};
|
||||
</script>
|
||||
|
||||
<svelte:window onkeydown={handleKeydown} />
|
||||
|
||||
<!-- Search Button (Header) -->
|
||||
<button
|
||||
class="btn btn-ghost gap-2"
|
||||
onclick={() => {
|
||||
open = true;
|
||||
setTimeout(() => searchInput?.focus(), 100);
|
||||
}}
|
||||
aria-label="Search"
|
||||
>
|
||||
<Search class="h-5 w-5" />
|
||||
<span class="hidden md:inline">Search</span>
|
||||
<kbd class="kbd kbd-sm hidden lg:inline-flex">
|
||||
<Command class="h-3 w-3" />
|
||||
K
|
||||
</kbd>
|
||||
</button>
|
||||
|
||||
<!-- Search Modal -->
|
||||
<Modal bind:open size="lg">
|
||||
<div class="space-y-4">
|
||||
<form onsubmit={handleSearch}>
|
||||
<label class="input input-bordered flex items-center gap-2">
|
||||
<Search class="h-5 w-5 text-base-content/60" />
|
||||
<input
|
||||
bind:this={searchInput}
|
||||
bind:value={query}
|
||||
type="text"
|
||||
class="grow"
|
||||
placeholder="Search matches, players, share codes..."
|
||||
autocomplete="off"
|
||||
/>
|
||||
<kbd class="kbd kbd-sm">
|
||||
<Command class="h-3 w-3" />
|
||||
K
|
||||
</kbd>
|
||||
</label>
|
||||
</form>
|
||||
|
||||
<!-- Recent Searches -->
|
||||
{#if $search.recentSearches.length > 0}
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="text-sm font-semibold text-base-content/70">Recent Searches</h3>
|
||||
<button class="btn btn-ghost btn-xs" onclick={handleClearRecent}>Clear</button>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{#each $search.recentSearches as recent}
|
||||
<button
|
||||
class="badge badge-lg badge-outline gap-2 hover:badge-primary"
|
||||
onclick={() => handleRecentClick(recent)}
|
||||
>
|
||||
<Search class="h-3 w-3" />
|
||||
{recent}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Search Tips -->
|
||||
<div class="rounded-lg bg-base-200 p-4">
|
||||
<h4 class="mb-2 text-sm font-semibold text-base-content">Search Tips</h4>
|
||||
<ul class="space-y-1 text-xs text-base-content/70">
|
||||
<li>• Search by player name or Steam ID</li>
|
||||
<li>• Enter share code to find specific match</li>
|
||||
<li>• Use map name to filter matches (e.g., "de_dust2")</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
Reference in New Issue
Block a user