Add profile and template pages (scaffolding)
This commit is contained in:
parent
41668498ea
commit
cb67c5d93d
18 changed files with 1067 additions and 158 deletions
|
|
@ -80,6 +80,7 @@
|
|||
|
||||
<main>
|
||||
<Navbar />
|
||||
<div style="height: var(--navbar-height);"></div> <!-- Spacer for fixed navbar -->
|
||||
<Map bind:this={map} mode="prediction" bind:data={$PredictionStore} on:coordinatesSelected={handleCoordinateSelection}>
|
||||
<PanelContainer bind:this={panelContainer} >
|
||||
<TabComponent
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
<main>
|
||||
<Navbar />
|
||||
<div style="height: var(--navbar-height);"></div> <!-- Spacer for fixed navbar -->
|
||||
<Map>
|
||||
<TelemetryPanel
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,267 @@
|
|||
<script lang="ts">
|
||||
import Navbar from '$lib/components/Navbar.svelte';
|
||||
import Navbar from "$lib/components/Navbar.svelte";
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardBody,
|
||||
Button,
|
||||
FormGroup,
|
||||
Label,
|
||||
Input,
|
||||
InputGroup,
|
||||
InputGroupText,
|
||||
Icon,
|
||||
} from "@sveltestrap/sveltestrap";
|
||||
|
||||
import ConfirmationPrompt from "$lib/components/ConfirmationPrompt.svelte";
|
||||
import Footer from "$lib/components/Footer.svelte";
|
||||
|
||||
let editMode = false;
|
||||
let showToken = false;
|
||||
|
||||
type ConfirmConfig = {
|
||||
title: string;
|
||||
body: string;
|
||||
confirmText: string;
|
||||
confirmVariant?: string;
|
||||
onConfirm: () => void;
|
||||
};
|
||||
|
||||
// State for the single confirmation prompt
|
||||
let showConfirm = false;
|
||||
let confirmConfig: ConfirmConfig = {
|
||||
title: "",
|
||||
body: "",
|
||||
confirmText: "",
|
||||
confirmVariant: "primary",
|
||||
onConfirm: () => {},
|
||||
};
|
||||
|
||||
function openConfirmation(config: Partial<ConfirmConfig>) {
|
||||
confirmConfig = { ...confirmConfig, ...config } as ConfirmConfig;
|
||||
showConfirm = true;
|
||||
}
|
||||
|
||||
function handleDeleteAccount() {
|
||||
openConfirmation({
|
||||
title: "Подтвердите удаление",
|
||||
body: "Вы уверены, что хотите удалить свою учетную запись? Это действие необратимо.",
|
||||
confirmText: "Удалить",
|
||||
confirmVariant: "danger",
|
||||
onConfirm: confirmDeleteAccount,
|
||||
});
|
||||
}
|
||||
|
||||
function handleResetSettings() {
|
||||
openConfirmation({
|
||||
title: "Подтвердите сброс",
|
||||
body: "Вы уверены, что хотите сбросить учетную запись? Это также удалит все сохранные сценарии, шаблоны и точки запуска.",
|
||||
confirmText: "Сбросить",
|
||||
confirmVariant: "warning",
|
||||
onConfirm: confirmResetSettings,
|
||||
});
|
||||
}
|
||||
|
||||
function handleGenerateToken() {
|
||||
openConfirmation({
|
||||
title: "Подтвердите создание токена",
|
||||
body: "Генерация нового токена API приведет к прекращению действия старого токена. Приложения, использующие старый токен, перестанут работать. Вы уверены, что хотите создать новый токен?",
|
||||
confirmText: "Создать",
|
||||
confirmVariant: "primary",
|
||||
onConfirm: confirmGenerateToken,
|
||||
});
|
||||
}
|
||||
|
||||
function handleLogout() {
|
||||
openConfirmation({
|
||||
title: "Подтвердите выход",
|
||||
body: "Вы уверены, что хотите выйти из учетной записи? Вы будете перенаправлены на страницу входа.",
|
||||
confirmText: "Выйти",
|
||||
onConfirm: confirmLogout,
|
||||
});
|
||||
}
|
||||
|
||||
function confirmDeleteAccount() {
|
||||
// Implement account deletion logic
|
||||
console.log("Account deleted");
|
||||
}
|
||||
|
||||
function confirmResetSettings() {
|
||||
// Implement settings reset logic
|
||||
console.log("Settings reset");
|
||||
}
|
||||
|
||||
function confirmGenerateToken() {
|
||||
// Implement token generation logic
|
||||
console.log("New token generated");
|
||||
}
|
||||
|
||||
function confirmLogout() {
|
||||
// Implement logout logic
|
||||
console.log("Logged out");
|
||||
}
|
||||
|
||||
function handleConfirm() {
|
||||
if (confirmConfig.onConfirm) {
|
||||
confirmConfig.onConfirm();
|
||||
}
|
||||
showConfirm = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<main class="force-page-height">
|
||||
<Navbar />
|
||||
<div class="container">
|
||||
<h1>User Account</h1>
|
||||
<p>Manage your account settings here.</p>
|
||||
<!-- Add account management components or links here -->
|
||||
<div style="height: var(--navbar-height);"></div>
|
||||
<!-- Spacer for fixed navbar -->
|
||||
<div class="container my-4">
|
||||
<div class="row">
|
||||
<!-- Side Navigation -->
|
||||
<div class="col-md-3 col-lg-2">
|
||||
<nav class="nav nav-pills flex-column">
|
||||
<a class="nav-link active" href="/user/account">Учетная запись</a>
|
||||
<a class="nav-link" href="/user/templates">Сохраненные сценарии</a>
|
||||
<a class="nav-link" href="#api-tokens">История прогнозов</a>
|
||||
<a class="nav-link" href="#actions">История слежения</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="col-md-9 col-lg-10">
|
||||
<!-- Account Information -->
|
||||
<Card class="mb-4">
|
||||
<CardHeader>
|
||||
<h5 class="mb-0">Основная информация</h5>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<FormGroup>
|
||||
<Label for="username">Имя пользователя:</Label>
|
||||
<Input id="username" value="user123" readonly disabled />
|
||||
</FormGroup>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<FormGroup>
|
||||
<Label for="email">Email:</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
value="user@example.com"
|
||||
readonly={!editMode}
|
||||
disabled={!editMode}
|
||||
/>
|
||||
</FormGroup>
|
||||
</div>
|
||||
</div>
|
||||
<FormGroup>
|
||||
<Label for="fullname">Полное имя:</Label>
|
||||
<Input id="fullname" value="Иван Иванов" readonly={!editMode} disabled={!editMode} />
|
||||
</FormGroup>
|
||||
{#if editMode}
|
||||
<Button color="success" on:click={() => (editMode = false)}>Сохранить</Button>
|
||||
<Button color="secondary" on:click={() => (editMode = false)}>Отменить</Button>
|
||||
{:else}
|
||||
<Button color="primary" on:click={() => (editMode = true)}>Редактировать</Button>
|
||||
{/if}
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
<!-- Password Change -->
|
||||
<Card class="mb-4">
|
||||
<CardHeader>
|
||||
<h5 class="mb-0">Смена пароля</h5>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<FormGroup>
|
||||
<Label for="currentPassword">Текущий пароль:</Label>
|
||||
<Input id="currentPassword" type="password" />
|
||||
</FormGroup>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<FormGroup>
|
||||
<Label for="newPassword">Новый пароль:</Label>
|
||||
<Input id="newPassword" type="password" />
|
||||
</FormGroup>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<FormGroup>
|
||||
<Label for="confirmPassword">Повтор пароля:</Label>
|
||||
<Input id="confirmPassword" type="password" />
|
||||
</FormGroup>
|
||||
</div>
|
||||
</div>
|
||||
<Button color="primary">Изменить пароль</Button>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
<!-- API Token -->
|
||||
<Card class="mb-4">
|
||||
<CardHeader>
|
||||
<h5 class="mb-0">Токен API</h5>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<FormGroup>
|
||||
<Label for="apiToken">Токен доступа</Label>
|
||||
<InputGroup>
|
||||
<div class="position-relative flex-grow-1">
|
||||
<Input
|
||||
id="apiToken"
|
||||
class="form-control pe-5"
|
||||
type={showToken ? "text" : "password"}
|
||||
value="abc123def456..."
|
||||
readonly
|
||||
/>
|
||||
<Button
|
||||
size="sm"
|
||||
color="white"
|
||||
class="position-absolute top-50 end-0 translate-middle-y me-2 rounded-circle d-flex align-items-center justify-content-center"
|
||||
style="width: 32px; height: 32px; border: none; color: var(--bs-secondary); z-index: 10;"
|
||||
on:click={() => {
|
||||
showToken = !showToken;
|
||||
}}
|
||||
>
|
||||
<Icon name={showToken ? "eye-slash" : "eye"} style="font-size: 16px;" />
|
||||
</Button>
|
||||
</div>
|
||||
<Button>
|
||||
<Icon name="clipboard" />
|
||||
</Button>
|
||||
</InputGroup>
|
||||
</FormGroup>
|
||||
<Button color="warning" on:click={handleGenerateToken}>Сгенерировать новый токен</Button>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
<!-- Account Actions -->
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<h5 class="mb-0">Действия с аккаунтом</h5>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<div class="d-grid gap-2 d-md-flex">
|
||||
<Button color="secondary" on:click={handleLogout}>Выйти</Button>
|
||||
<!-- spacer -->
|
||||
<span class="d-none d-md-inline-block flex-grow-1"></span>
|
||||
<Button color="warning" on:click={handleResetSettings}>Сбросить настройки</Button>
|
||||
<Button color="danger" on:click={handleDeleteAccount}>Удалить аккаунт</Button>
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
</main>
|
||||
|
||||
<!-- Single Dynamic Confirmation Prompt -->
|
||||
<ConfirmationPrompt
|
||||
bind:isOpen={showConfirm}
|
||||
title={confirmConfig.title}
|
||||
confirmText={confirmConfig.confirmText}
|
||||
confirmVariant={confirmConfig.confirmVariant || "primary"}
|
||||
cancelText="Отмена"
|
||||
onconfirm={handleConfirm}
|
||||
oncancel={() => (showConfirm = false)}
|
||||
>
|
||||
<p>{confirmConfig.body}</p>
|
||||
</ConfirmationPrompt>
|
||||
|
|
|
|||
345
src/routes/user/templates/+page.svelte
Normal file
345
src/routes/user/templates/+page.svelte
Normal file
|
|
@ -0,0 +1,345 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { TableHandler } from "@vincjo/datatables";
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardBody,
|
||||
Button,
|
||||
Input,
|
||||
Icon,
|
||||
Pagination,
|
||||
PaginationItem,
|
||||
PaginationLink,
|
||||
} from "@sveltestrap/sveltestrap";
|
||||
|
||||
import Navbar from "$lib/components/Navbar.svelte";
|
||||
import Footer from "$lib/components/Footer.svelte";
|
||||
import ConfirmationPrompt from "$lib/components/ConfirmationPrompt.svelte";
|
||||
import { addToast } from "$lib/components/Toast.svelte";
|
||||
|
||||
// TODO: Implement these imports
|
||||
import { SavedPointsStore, SavedFlightProfilesStore, SavedScenarioTemplatesStore } from "$lib/stores";
|
||||
import { getSavedPoints, deletePoint } from "$lib/api/points";
|
||||
import { getSavedFlightProfiles, deleteFlightProfile } from "$lib/api/profiles";
|
||||
import { getSavedScenarioTemplates, deleteScenarioTemplate } from "$lib/api/templates";
|
||||
import type { SavedPoint, SavedFlightProfile, SavedScenarioTemplate } from "$lib/types";
|
||||
|
||||
// Table handlers
|
||||
let pointsTable = $derived(new TableHandler($SavedPointsStore, { rowsPerPage: 5 }));
|
||||
let pointsSearch = $derived(pointsTable.createSearch(["name"]));
|
||||
|
||||
let profilesTable = $derived(new TableHandler($SavedFlightProfilesStore, { rowsPerPage: 5 }));
|
||||
let profilesSearch = $derived(profilesTable.createSearch(["name"]));
|
||||
|
||||
let templatesTable = $derived(new TableHandler($SavedScenarioTemplatesStore, { rowsPerPage: 5 }));
|
||||
let templatesSearch = $derived(templatesTable.createSearch(["name"]));
|
||||
|
||||
onMount(async () => {
|
||||
// Mock data for demonstration. Replace with API calls.
|
||||
$SavedPointsStore = [
|
||||
{ id: 1, name: "Baikonur Cosmodrome", lat: 45.96, lon: 63.3, alt: 90 },
|
||||
{ id: 2, name: "Kennedy Space Center", lat: 28.57, lon: -80.64, alt: 3 },
|
||||
];
|
||||
$SavedFlightProfilesStore = [
|
||||
{ id: 1, name: "Standard Weather Balloon", rate_profile_data: {ascent_rate: 5, descent_rate: 8, burst_altitude: 30000} },
|
||||
{ id: 2, name: "High Altitude Probe", rate_profile_data: {ascent_rate: 6, descent_rate: 10, burst_altitude: 40000} },
|
||||
];
|
||||
$SavedScenarioTemplatesStore = [
|
||||
{ id: 1, name: "Summer Launch from Baikonur", template_data: {description: "Standard summer conditions test."} },
|
||||
{ id: 2, name: "Winter Launch from KSC", template_data: {description: "High wind scenario."} },
|
||||
];
|
||||
|
||||
/*
|
||||
// TODO: Uncomment when API is ready
|
||||
const [points, profiles, templates] = await Promise.all([
|
||||
getSavedPoints(),
|
||||
getSavedFlightProfiles(),
|
||||
getSavedScenarioTemplates()
|
||||
]);
|
||||
$SavedPointsStore = points;
|
||||
$SavedFlightProfilesStore = profiles;
|
||||
$SavedScenarioTemplatesStore = templates;
|
||||
*/
|
||||
});
|
||||
|
||||
// --- Confirmation Prompt Logic ---
|
||||
type ConfirmConfig = {
|
||||
title: string;
|
||||
body: string;
|
||||
confirmText: string;
|
||||
confirmVariant?: string;
|
||||
onConfirm: () => void;
|
||||
};
|
||||
let showConfirm = $state(false);
|
||||
let confirmConfig = $state<ConfirmConfig>({
|
||||
title: "",
|
||||
body: "",
|
||||
confirmText: "",
|
||||
onConfirm: () => {},
|
||||
});
|
||||
|
||||
function openConfirmation(config: Partial<ConfirmConfig>) {
|
||||
confirmConfig = { ...confirmConfig, ...config } as ConfirmConfig;
|
||||
showConfirm = true;
|
||||
}
|
||||
|
||||
function handleConfirm() {
|
||||
if (confirmConfig.onConfirm) {
|
||||
confirmConfig.onConfirm();
|
||||
}
|
||||
showConfirm = false;
|
||||
}
|
||||
|
||||
// --- Delete Handlers ---
|
||||
function handleDelete<T extends { id: number; name: string }>(
|
||||
item: T,
|
||||
deleteFn: (id: number) => Promise<any>,
|
||||
store: any,
|
||||
itemName: string,
|
||||
) {
|
||||
openConfirmation({
|
||||
title: `Подтвердите удаление`,
|
||||
body: `Вы уверены, что хотите удалить ${itemName} "${item.name}"?`,
|
||||
confirmText: "Удалить",
|
||||
confirmVariant: "danger",
|
||||
onConfirm: () => {
|
||||
// deleteFn(item.id).then(() => { // TODO: Uncomment when API is ready
|
||||
store.update((items: T[]) => items.filter((i) => i.id !== item.id));
|
||||
addToast({
|
||||
header: `${itemName} удален`,
|
||||
body: `${itemName} "${item.name}" успешно удален.`,
|
||||
color: "success",
|
||||
});
|
||||
// }).catch(error => addToast({ header: 'Ошибка', body: `Не удалось удалить ${itemName}: ${error.message}`, color: 'danger' }));
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<main class="force-page-height">
|
||||
<Navbar />
|
||||
<div style="height: var(--navbar-height);"></div>
|
||||
<!-- Spacer for fixed navbar -->
|
||||
<div class="container my-4">
|
||||
<div class="row">
|
||||
<!-- Side Navigation -->
|
||||
<div class="col-md-3 col-lg-2">
|
||||
<nav class="nav nav-pills flex-column">
|
||||
<a class="nav-link" href="/user/account">Учетная запись</a>
|
||||
<a class="nav-link active" href="/user/templates">Сохраненные сценарии</a>
|
||||
<a class="nav-link" href="#/">История прогнозов</a>
|
||||
<a class="nav-link" href="#/">История слежения</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="col-md-9 col-lg-10">
|
||||
<!-- Saved Points -->
|
||||
<Card class="mb-4">
|
||||
<CardHeader>
|
||||
<h5 class="mb-0">Точки запуска</h5>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<Input
|
||||
type="text"
|
||||
class="form-control-sm mb-2"
|
||||
placeholder="Поиск по названию..."
|
||||
bind:value={pointsSearch.value}
|
||||
oninput={() => pointsSearch.set()}
|
||||
/>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Название</th>
|
||||
<th>Широта</th>
|
||||
<th>Долгота</th>
|
||||
<th>Высота</th>
|
||||
<th class="fit"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each pointsTable.rows as row}
|
||||
<tr>
|
||||
<td>{row.name}</td>
|
||||
<td>{row.lat.toFixed(4)} °</td>
|
||||
<td>{row.lon.toFixed(4)} °</td>
|
||||
<td>{row.alt} м</td>
|
||||
<td class="fit">
|
||||
<Button
|
||||
color="danger"
|
||||
size="sm"
|
||||
onclick={() =>
|
||||
handleDelete(row, deletePoint, SavedPointsStore, "Точка")}
|
||||
>
|
||||
<Icon name="trash" />
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<Pagination aria-label="Points page navigation" size="sm">
|
||||
<PaginationItem>
|
||||
<PaginationLink previous onclick={() => pointsTable.setPage("previous")} />
|
||||
</PaginationItem>
|
||||
{#each pointsTable.pagesWithEllipsis as page}
|
||||
<PaginationItem active={pointsTable.currentPage === page}>
|
||||
<PaginationLink onclick={() => pointsTable.setPage(page)}>{page}</PaginationLink>
|
||||
</PaginationItem>
|
||||
{/each}
|
||||
<PaginationItem>
|
||||
<PaginationLink next onclick={() => pointsTable.setPage("next")} />
|
||||
</PaginationItem>
|
||||
</Pagination>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
<!-- Saved Flight Profiles -->
|
||||
<Card class="mb-4">
|
||||
<CardHeader>
|
||||
<h5 class="mb-0">Профили полета</h5>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<Input
|
||||
type="text"
|
||||
class="form-control-sm mb-2"
|
||||
placeholder="Поиск по названию..."
|
||||
bind:value={profilesSearch.value}
|
||||
oninput={() => profilesSearch.set()}
|
||||
/>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Название</th>
|
||||
<th>Скороподъемность</th>
|
||||
<th>Скорость снижения</th>
|
||||
<th>Высота разрыва</th>
|
||||
<th class="fit"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each profilesTable.rows as row}
|
||||
<tr>
|
||||
<td>{row.name}</td>
|
||||
<td>{row.rate_profile_data.ascent_rate} м/с</td>
|
||||
<td>{row.rate_profile_data.descent_rate} м/с</td>
|
||||
<td>{row.rate_profile_data.burst_altitude} м</td>
|
||||
<td class="fit">
|
||||
<Button
|
||||
color="danger"
|
||||
size="sm"
|
||||
onclick={() =>
|
||||
handleDelete(
|
||||
row,
|
||||
deleteFlightProfile,
|
||||
SavedFlightProfilesStore,
|
||||
"Профиль",
|
||||
)}
|
||||
>
|
||||
<Icon name="trash" />
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<Pagination aria-label="Profiles page navigation" size="sm">
|
||||
<PaginationItem>
|
||||
<PaginationLink previous onclick={() => profilesTable.setPage("previous")} />
|
||||
</PaginationItem>
|
||||
{#each profilesTable.pagesWithEllipsis as page}
|
||||
<PaginationItem active={profilesTable.currentPage === page}>
|
||||
<PaginationLink onclick={() => profilesTable.setPage(page)}>{page}</PaginationLink>
|
||||
</PaginationItem>
|
||||
{/each}
|
||||
<PaginationItem>
|
||||
<PaginationLink next onclick={() => profilesTable.setPage("next")} />
|
||||
</PaginationItem>
|
||||
</Pagination>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
<!-- Saved Scenario Templates -->
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<h5 class="mb-0">Сценарии</h5>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<Input
|
||||
type="text"
|
||||
class="form-control-sm mb-2"
|
||||
placeholder="Поиск по названию..."
|
||||
bind:value={templatesSearch.value}
|
||||
oninput={() => templatesSearch.set()}
|
||||
/>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Название</th>
|
||||
<th>Описание</th>
|
||||
<th class="fit"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each templatesTable.rows as row}
|
||||
<tr>
|
||||
<td>{row.name}</td>
|
||||
<td>{row.template_data.description}</td>
|
||||
<td class="fit">
|
||||
<Button
|
||||
color="danger"
|
||||
size="sm"
|
||||
onclick={() =>
|
||||
handleDelete(
|
||||
row,
|
||||
deleteScenarioTemplate,
|
||||
SavedScenarioTemplatesStore,
|
||||
"Шаблон",
|
||||
)}
|
||||
>
|
||||
<Icon name="trash" />
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<Pagination aria-label="Templates page navigation" size="sm">
|
||||
<PaginationItem>
|
||||
<PaginationLink previous onclick={() => templatesTable.setPage("previous")} />
|
||||
</PaginationItem>
|
||||
{#each templatesTable.pagesWithEllipsis as page}
|
||||
<PaginationItem active={templatesTable.currentPage === page}>
|
||||
<PaginationLink onclick={() => templatesTable.setPage(page)}>{page}</PaginationLink>
|
||||
</PaginationItem>
|
||||
{/each}
|
||||
<PaginationItem>
|
||||
<PaginationLink next onclick={() => templatesTable.setPage("next")} />
|
||||
</PaginationItem>
|
||||
</Pagination>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Footer />
|
||||
</main>
|
||||
|
||||
<ConfirmationPrompt
|
||||
bind:isOpen={showConfirm}
|
||||
title={confirmConfig.title}
|
||||
confirmText={confirmConfig.confirmText}
|
||||
confirmVariant={confirmConfig.confirmVariant || "danger"}
|
||||
cancelText="Отмена"
|
||||
onconfirm={handleConfirm}
|
||||
oncancel={() => (showConfirm = false)}
|
||||
>
|
||||
<p>{confirmConfig.body}</p>
|
||||
</ConfirmationPrompt>
|
||||
Loading…
Add table
Add a link
Reference in a new issue