Confirmations

This commit is contained in:
ThePetrovich 2025-07-03 20:39:17 +08:00
parent cb67c5d93d
commit 551951827d
6 changed files with 283 additions and 25 deletions

View file

@ -1,8 +1,197 @@
<script lang="ts">
import { Datatable, Search, RowsPerPage, RowCount, Pagination } from '@vincjo/datatables'
import { Modal } from '@sveltestrap/sveltestrap';
import { onMount } from 'svelte';
import { TableHandler } from "@vincjo/datatables";
import {
Modal,
Button,
FormGroup,
Label,
Input,
Alert,
Icon,
Pagination,
PaginationItem,
PaginationLink,
} from "@sveltestrap/sveltestrap";
import { onMount } from "svelte";
import { addToast } from "$lib/components/Toast.svelte";
import type { SavedPoint } from "$lib/types";
import { SavedPointsStore } from "$lib/stores";
import ConfirmationPrompt from "./ConfirmationPrompt.svelte";
import { getSavedPoints, savePoint, updatePoint, deletePoint } from "$lib/api/points";
// Props
let { isOpen = $bindable(false), onClose = () => {}, onChange = () => {}, point} = $props();
// Runes
let selectedPoint = $derived<SavedPoint>(point);
let isAlertVisible = $state(false);
let isConfirmationVisible = $state(false);
let alertText = $state("");
</script>
$effect(() => {
onChange();
});
// Modal controls
export function openModal() {
isOpen = true;
}
function closeModal() {
isOpen = false;
onClose();
}
function handleDeletePoint(point: SavedPoint) {
deletePoint(point.id)
.then(() => {
$SavedPointsStore = $SavedPointsStore.filter((p) => p.id !== point.id);
SavedPointsStore.set($SavedPointsStore);
resetForm();
addToast({
header: "Точка удалена",
body: `Точка "${point.name}" успешно удалена.`,
color: "success",
});
})
.catch((error) => {
showAlert(`Ошибка при удалении точки: ${error.message}`);
console.error("Ошибка при удалении точки:", error);
});
}
function handleSavePoint() {
updatePoint(selectedPoint)
.then((updatedPoint) => {
$SavedPointsStore = $SavedPointsStore.map((p) => (p.id === updatedPoint.id ? updatedPoint : p));
SavedPointsStore.set($SavedPointsStore);
resetForm();
addToast({
header: "Точка обновлена",
body: `Точка "${updatedPoint.name}" успешно обновлена.`,
color: "success",
});
})
.catch((error) => {
showAlert(`Ошибка при обновлении точки: ${error.message}`);
});
}
export function showAlert(message: string) {
isAlertVisible = true;
alertText = message;
}
export function hideAlert() {
isAlertVisible = false;
alertText = "";
}
export function resetForm() {
hideAlert();
closeModal();
}
</script>
<Modal {isOpen} toggle={closeModal} size="lg" fade={false} backdrop={true} scrollable class={ isConfirmationVisible ? "modal-tinted" : ""}>
<div class="modal-header">
<h5 class="modal-title">Редактирование точки</h5>
<button type="button" class="btn-close" onclick={closeModal} aria-label="Close"></button>
</div>
<div class="modal-body">
<div>
<h5>{"Редактирование точки"}</h5>
<Alert
color="danger"
isOpen={isAlertVisible}
toggle={() => (isAlertVisible = false)}
fade={false}
class="mb-2"
>
<Icon name="exclamation-triangle" class="me-2" />
{alertText}
</Alert>
<form
onsubmit={(e) => {
e.preventDefault();
handleSavePoint();
}}
>
<div class="mb-2">
<Label for="name" class="small">Название точки:</Label>
<Input class="form-control-sm" type="text" id="name" bind:value={selectedPoint.name} required />
</div>
<div class="d-flex gap-2">
<FormGroup class="flex-grow-1">
<Label for="lat" class="small">Широта:</Label>
<Input
class="form-control-sm"
type="number"
step="any"
id="lat"
bind:value={selectedPoint.lat}
required
/>
<span class="form-text">Градусы</span>
</FormGroup>
<FormGroup class="flex-grow-1">
<Label for="lon" class="small">Долгота:</Label>
<Input
class="form-control-sm"
type="number"
step="any"
id="lon"
bind:value={selectedPoint.lon}
required
/>
<span class="form-text">Градусы</span>
</FormGroup>
<FormGroup class="flex-grow-1">
<Label for="alt" class="small">Высота:</Label>
<Input
class="form-control-sm"
type="number"
step="any"
id="alt"
bind:value={selectedPoint.alt}
required
/>
<span class="form-text">Метры над ур. моря</span>
</FormGroup>
</div>
<div class="d-grid gap-2 d-md-flex">
<Button type="submit" color="success" size="sm">
Обновить точку
</Button>
<Button size="sm" type="button" color="secondary" onclick={resetForm}>Отмена</Button>
<span class="flex-grow-1"></span>
<Button
type="button"
color="danger"
size="sm"
onclick={() => isConfirmationVisible = true}
>
Удалить точку
</Button>
</div>
</form>
</div>
</div>
</Modal>
<ConfirmationPrompt
isOpen={isConfirmationVisible}
title="Подтвердите удаление"
confirmText="Удалить"
cancelText="Отмена"
confirmVariant="danger"
onconfirm={() => {
isConfirmationVisible = false;
handleDeletePoint(selectedPoint);
}}
oncancel={() => {
isConfirmationVisible = false;
}}
>
<p>Вы уверены, что хотите удалить эту точку?</p>
</ConfirmationPrompt>

View file

@ -16,16 +16,24 @@
import { addToast } from "$lib/components/Toast.svelte";
import type { SavedPoint } from "$lib/types";
import { SavedPointsStore } from "$lib/stores";
import ConfirmationPrompt from "./ConfirmationPrompt.svelte";
import { getSavedPoints, savePoint, updatePoint, deletePoint } from "$lib/api/points";
// Props
let { isOpen = $bindable(false), onClose = () => {}, onChange = () => {} } = $props();
let {
isOpen = $bindable(false),
onClose = () => {},
onChange = () => {},
point = null,
coordinates = { id: 0, name: "", lat: 0, lon: 0, alt: 0 },
} = $props();
// Runes
let selectedPoint = $state<SavedPoint | null>(null);
let newPoint = $state<SavedPoint>({ id: 0, name: "", lat: 0, lon: 0, alt: 0 });
let selectedPoint = $state<SavedPoint | null>(point);
let newPoint = $state<SavedPoint>(coordinates as SavedPoint);
let isEditing = $state(false);
let isAlertVisible = $state(false);
let isConfirmationVisible = $state(false);
let alertText = $state("");
// Table handler
@ -59,7 +67,13 @@
isEditing = true;
}
function handleDeletePoint(point: SavedPoint) {
function confirmDeletePoint(point: SavedPoint) {
selectedPoint = point;
isConfirmationVisible = true;
}
function handleDeletePoint(point: SavedPoint | null) {
if (!point) return;
deletePoint(point.id)
.then(() => {
$SavedPointsStore = $SavedPointsStore.filter((p) => p.id !== point.id);
@ -129,7 +143,7 @@
}
</script>
<Modal {isOpen} toggle={closeModal} size="lg" fade={false} backdrop={true} scrollable>
<Modal {isOpen} toggle={closeModal} size="lg" fade={false} backdrop={true} scrollable class={isConfirmationVisible ? "modal-tinted" : ""}>
<div class="modal-header">
<h5 class="modal-title">Сохраненные точки</h5>
<button type="button" class="btn-close" onclick={closeModal} aria-label="Close"></button>
@ -179,7 +193,7 @@
<Button color="primary" size="sm" onclick={() => handleEditPoint(row)}>
<Icon name="pencil" />
</Button>
<Button color="danger" size="sm" onclick={() => handleDeletePoint(row)}>
<Button color="danger" size="sm" onclick={() => confirmDeletePoint(row)}>
<Icon name="trash" />
</Button>
</td>
@ -275,3 +289,20 @@
</div>
</div>
</Modal>
<ConfirmationPrompt
isOpen={isConfirmationVisible}
title="Подтвердите удаление"
confirmText="Удалить"
cancelText="Отмена"
confirmVariant="danger"
onconfirm={() => {
isConfirmationVisible = false;
handleDeletePoint(selectedPoint);
}}
oncancel={() => {
isConfirmationVisible = false;
}}
>
<p>Вы уверены, что хотите удалить эту точку?</p>
</ConfirmationPrompt>

View file

@ -86,7 +86,7 @@
Войти
{/if}
</button>
<a href="/predict" class="btn btn-secondary mt-3 w-100">Назад</a>
<a href="/" class="btn btn-secondary mt-3 w-100">Назад</a>
</form>
</div>
</div>

View file

@ -116,7 +116,7 @@
<div class="container my-4">
<div class="row">
<!-- Side Navigation -->
<div class="col-md-3 col-lg-2">
<div class="col-md-3 col-lg-2 mb-4">
<nav class="nav nav-pills flex-column">
<a class="nav-link active" href="/user/account">Учетная запись</a>
<a class="nav-link" href="/user/templates">Сохраненные сценарии</a>

View file

@ -16,6 +16,8 @@
import Navbar from "$lib/components/Navbar.svelte";
import Footer from "$lib/components/Footer.svelte";
import ConfirmationPrompt from "$lib/components/ConfirmationPrompt.svelte";
import PointEditor from "$lib/components/PointEditor.svelte";
import ToastContainer from "$lib/components/Toast.svelte";
import { addToast } from "$lib/components/Toast.svelte";
// TODO: Implement these imports
@ -35,12 +37,14 @@
let templatesTable = $derived(new TableHandler($SavedScenarioTemplatesStore, { rowsPerPage: 5 }));
let templatesSearch = $derived(templatesTable.createSearch(["name"]));
let editPoint: SavedPoint | null = $state(null);
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 },
];
const pts = await getSavedPoints();
$SavedPointsStore = pts;
SavedPointsStore.set($SavedPointsStore);
$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} },
@ -115,6 +119,10 @@
},
});
}
function handleEditPoint(point: SavedPoint) {
editPoint = point;
}
</script>
<main class="force-page-height">
@ -124,7 +132,7 @@
<div class="container my-4">
<div class="row">
<!-- Side Navigation -->
<div class="col-md-3 col-lg-2">
<div class="col-md-3 col-lg-2 mb-4">
<nav class="nav nav-pills flex-column">
<a class="nav-link" href="/user/account">Учетная запись</a>
<a class="nav-link active" href="/user/templates">Сохраненные сценарии</a>
@ -141,13 +149,28 @@
<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="position-relative mb-2">
<Input
type="text"
class="form-control-sm pe-5"
placeholder="Поиск по названию..."
bind:value={pointsSearch.value}
oninput={() => pointsSearch.set()}
/>
<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: 16px; height: 16px; border: none; background: var(--bs-secondary); color: var(--bs-white);"
onclick={() => {
pointsSearch.value = "";
pointsSearch.set();
}}
disabled={!pointsSearch.value}
>
<Icon name="x" style="font-size: 16px;" />
</Button>
</div>
<div class="table-responsive">
<table class="table table-sm">
<thead>
@ -167,6 +190,9 @@
<td>{row.lon.toFixed(4)} °</td>
<td>{row.alt} м</td>
<td class="fit">
<Button color="primary" size="sm" onclick={() => handleEditPoint(row)}>
<Icon name="pencil" />
</Button>
<Button
color="danger"
size="sm"
@ -342,4 +368,12 @@
oncancel={() => (showConfirm = false)}
>
<p>{confirmConfig.body}</p>
</ConfirmationPrompt>
</ConfirmationPrompt>
<PointEditor
point={editPoint}
isOpen={editPoint !== null}
onClose={() => { editPoint = null; pointsTable.setRows($SavedPointsStore) }}
/>
<ToastContainer />

View file

@ -128,6 +128,10 @@
flex-direction: column;
}
.modal-tinted {
filter: brightness(0.6);
}
@media (max-width: 767.98px)
{
.coordinates-display {