feat: continue impemelent mvp components and stores
This commit is contained in:
@@ -0,0 +1,285 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { panelStore } from "../stores/panelStore";
|
||||
import { panelStorage } from "../lib/utils/panelStorage";
|
||||
import type { Panel } from "../lib/types/panel";
|
||||
|
||||
export let onPanelSelect: (panel: Panel) => void;
|
||||
export let onPanelDelete: (panelId: string) => void;
|
||||
|
||||
let panels = $state<Panel[]>([]);
|
||||
let selectedPanelId = $state<string | null>(null);
|
||||
let errorMessage = $state<string | null>(null);
|
||||
|
||||
onMount(() => {
|
||||
loadPanels();
|
||||
});
|
||||
|
||||
function loadPanels() {
|
||||
try {
|
||||
errorMessage = null;
|
||||
panels = panelStorage.getAllPanels();
|
||||
|
||||
// Если есть текущая панель в store, отмечаем её как выбранную
|
||||
const currentPanel = $panelStore;
|
||||
if (currentPanel) {
|
||||
selectedPanelId = currentPanel.id;
|
||||
}
|
||||
} catch (error) {
|
||||
errorMessage = error instanceof Error ? error.message : "Ошибка загрузки панелей";
|
||||
}
|
||||
}
|
||||
|
||||
function handlePanelSelect(panel: Panel) {
|
||||
selectedPanelId = panel.id;
|
||||
onPanelSelect(panel);
|
||||
}
|
||||
|
||||
function handlePanelDelete(panelId: string) {
|
||||
try {
|
||||
errorMessage = null;
|
||||
const result = panelStorage.deletePanel(panelId);
|
||||
|
||||
if (result.success) {
|
||||
panels = panels.filter((p) => p.id !== panelId);
|
||||
|
||||
// Если удалили выбранную панель, сбрасываем выбор
|
||||
if (selectedPanelId === panelId) {
|
||||
selectedPanelId = null;
|
||||
}
|
||||
|
||||
// Уведомляем родительский компонент
|
||||
onPanelDelete(panelId);
|
||||
} else {
|
||||
errorMessage = result.error || "Ошибка удаления панели";
|
||||
}
|
||||
} catch (error) {
|
||||
errorMessage = error instanceof Error ? error.message : "Ошибка удаления панели";
|
||||
}
|
||||
}
|
||||
|
||||
function formatDate(date: Date): string {
|
||||
return new Intl.DateTimeFormat("ru-RU", {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
}).format(date);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="panel-list">
|
||||
<div class="panel-list-header">
|
||||
<h2>Сохраненные панели</h2>
|
||||
<button class="btn btn-refresh" on:click={loadPanels} aria-label="Обновить список">
|
||||
↻
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if errorMessage}
|
||||
<div class="error-message">
|
||||
{errorMessage}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if panels.length === 0}
|
||||
<div class="empty-state">
|
||||
<p>Нет сохраненных панелей</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="panels-grid">
|
||||
{#each panels as panel (panel.id)}
|
||||
<div
|
||||
class="panel-card {selectedPanelId === panel.id ? 'selected' : ''}"
|
||||
on:click={() => handlePanelSelect(panel)}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div class="panel-preview">
|
||||
{#if panel.backgroundImage}
|
||||
<img
|
||||
src={panel.backgroundImage}
|
||||
alt="Panel preview"
|
||||
class="panel-image"
|
||||
/>
|
||||
{:else}
|
||||
<div class="no-image">Нет изображения</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="panel-info">
|
||||
<div class="panel-meta">
|
||||
<span class="panel-id">ID: {panel.id.slice(0, 8)}...</span>
|
||||
<span class="panel-date">{formatDate(panel.updatedAt)}</span>
|
||||
</div>
|
||||
|
||||
<div class="panel-actions">
|
||||
<button
|
||||
class="btn-icon btn-delete"
|
||||
on:click|stopPropagation={() => handlePanelDelete(panel.id)}
|
||||
aria-label="Удалить панель"
|
||||
>
|
||||
🗑️
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.panel-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.panel-list-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
background: #f9f9f9;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.panel-list-header h2 {
|
||||
margin: 0;
|
||||
color: #333;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.btn-refresh {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 1.5rem;
|
||||
cursor: pointer;
|
||||
padding: 0.5rem;
|
||||
color: #666;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-refresh:hover {
|
||||
color: #007bff;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background: #ffebee;
|
||||
color: #c62828;
|
||||
padding: 0.75rem;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ffcdd2;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
color: #666;
|
||||
background: #f9f9f9;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.panels-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.panel-card {
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.panel-card:hover {
|
||||
border-color: #007bff;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.panel-card.selected {
|
||||
border-color: #007bff;
|
||||
background: #f0f8ff;
|
||||
}
|
||||
|
||||
.panel-preview {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding-top: 31.25%; /* 320:100 aspect ratio */
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.panel-image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.no-image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #999;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.panel-info {
|
||||
padding: 0.75rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.panel-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 0.8rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.panel-id {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.panel-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 1.2rem;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-delete:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user