feat: implement timer screens and navigation

This commit is contained in:
Arduinum
2026-06-04 22:12:26 +03:00
parent 632bf8202f
commit f73be8c219
11 changed files with 926 additions and 31 deletions

View File

@@ -31,6 +31,44 @@
margin: 0 auto;
}
.confirm-overlay {
position: fixed;
inset: 0;
background: rgba(15, 23, 42, 0.35);
display: flex;
align-items: center;
justify-content: center;
padding: 16px;
z-index: 1000;
}
.confirm-card {
width: 100%;
max-width: 380px;
background: #ffffff;
border-radius: 16px;
box-shadow: 0 12px 28px rgba(0, 0, 0, 0.18);
padding: 18px;
}
.confirm-card h3 {
font-size: 18px;
color: #212121;
margin-bottom: 8px;
}
.confirm-card p {
font-size: 14px;
color: #666;
margin-bottom: 16px;
}
.confirm-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
}
.loading,
.error-message,
.empty-state {
@@ -179,6 +217,27 @@
font-weight: 400;
}
.add-timer-button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
width: 100%;
min-height: 48px;
margin-bottom: 12px;
border: 2px solid #1976d2;
border-radius: 999px;
background-color: transparent;
color: #1976d2;
font-size: 14px;
font-weight: 600;
cursor: pointer;
}
.add-timer-button:hover {
background-color: rgba(25, 118, 210, 0.10);
}
.card-actions {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
@@ -191,8 +250,8 @@
justify-content: center;
gap: 6px;
padding: 10px 12px;
border: none;
border-radius: 4px;
border: 2px solid transparent;
border-radius: 999px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
@@ -207,43 +266,46 @@
}
.btn-primary {
background-color: #1976d2;
color: #ffffff;
background-color: transparent;
color: #4caf50;
border: 2px solid #4caf50;
}
.btn-primary:hover {
background-color: #1565c0;
box-shadow: 0 4px 8px rgba(25, 118, 210, 0.3);
background-color: rgba(76, 175, 80, 0.10);
box-shadow: 0 4px 8px rgba(76, 175, 80, 0.18);
}
.btn-primary:active {
background-color: #0d47a1;
background-color: rgba(76, 175, 80, 0.20);
}
.btn-secondary {
background-color: #e0e0e0;
color: #212121;
background-color: transparent;
color: #f9a825;
border: 2px solid #f9a825;
}
.btn-secondary:hover {
background-color: #d0d0d0;
background-color: rgba(249, 168, 37, 0.10);
}
.btn-secondary:active {
background-color: #c0c0c0;
background-color: rgba(249, 168, 37, 0.20);
}
.btn-danger {
background-color: #fff3e0;
color: #e65100;
background-color: transparent;
color: #f44336;
border: 2px solid #f44336;
}
.btn-danger:hover {
background-color: #ffe0b2;
background-color: rgba(244, 67, 54, 0.10);
}
.btn-danger:active {
background-color: #ffcc80;
background-color: rgba(244, 67, 54, 0.20);
}
@media (max-width: 600px) {

View File

@@ -6,6 +6,21 @@
</div>
<div class="timers-list">
<div v-if="confirmDeleteId !== null" class="confirm-overlay" @click.self="cancelDeleteTimer">
<div class="confirm-card">
<h3>Удалить таймер?</h3>
<p>Это действие нельзя будет отменить.</p>
<div class="confirm-actions">
<button class="btn btn-secondary" type="button" @click="cancelDeleteTimer">Отмена</button>
<button class="btn btn-danger" type="button" @click="removeTimer">Удалить</button>
</div>
</div>
</div>
<button @click="goToCreateTimer" class="add-timer-button" type="button">
<ion-icon :icon="addCircleOutline"></ion-icon>
Создать таймер
</button>
<!-- Loading state -->
<div v-if="loading" class="loading">
<p>Загрузка таймеров...</p>
@@ -63,15 +78,15 @@
</div>
<div class="card-actions">
<button @click="goToTimer(timer.id)" class="btn btn-secondary">
<button @click="goToTimer(timer.id)" class="btn btn-primary">
<ion-icon :icon="playCircleOutline"></ion-icon>
Открыть
</button>
<button class="btn btn-secondary">
<button @click="goToEditTimer(timer.id)" class="btn btn-secondary">
<ion-icon :icon="pencilOutline"></ion-icon>
Редактировать
</button>
<button class="btn btn-danger">
<button @click="askDeleteTimer(timer.id)" class="btn btn-danger">
<ion-icon :icon="trashOutline"></ion-icon>
Удалить
</button>

View File

@@ -1,6 +1,6 @@
import { defineComponent, onMounted, ref } from 'vue'
import { defineComponent, onMounted, ref, watch } from 'vue'
import { IonIcon, IonPage, IonContent } from '@ionic/vue'
import { useRouter } from 'vue-router';
import { useRoute, useRouter } from 'vue-router'
import {
timerOutline,
chevronUp,
@@ -8,26 +8,76 @@ import {
playCircleOutline,
pencilOutline,
trashOutline,
addCircleOutline,
} from 'ionicons/icons'
import { getTimers } from '@/db/crud/timerCrud'
import { deleteTimer, getTimers } from '@/db/crud/timerCrud'
import type { TimersRow } from '@/types/timerType'
export default defineComponent({
name: 'TimersList',
components: { IonIcon, IonPage, IonContent },
setup() {
/** Список всех таймеров */
const timers = ref<TimersRow[]>([])
/** Флаг загрузки списка таймеров */
const loading = ref<boolean>(true)
/** Ошибка загрузки или операций */
const error = ref<string | null>(null)
/** ID раскрытого таймера в списке */
const expandedId = ref<number | null>(null)
const router = useRouter();
/** ID таймера, ожидающего удаления */
const confirmDeleteId = ref<number | null>(null)
const router = useRouter()
const route = useRoute()
/** Переход на страницу конкретного таймера */
const goToTimer = (id: number) => {
router.push(`/timer/${id}`);
};
router.push(`/timer/${id}`)
}
/** Переход на создание нового таймера */
const goToCreateTimer = () => {
router.push('/timer-update/new')
}
/** Переход на редактирование таймера */
const goToEditTimer = (id: number) => {
router.push(`/timer-update/${id}`)
}
/** Открывает подтверждение удаления таймера */
const askDeleteTimer = (id: number): void => {
confirmDeleteId.value = id
}
/** Отмена удаления таймера */
const cancelDeleteTimer = (): void => {
confirmDeleteId.value = null
}
/** Удаляет таймер и обновляет список */
const removeTimer = async (): Promise<void> => {
if (confirmDeleteId.value === null) {
return
}
try {
await deleteTimer(confirmDeleteId.value)
confirmDeleteId.value = null
await loadTimers()
} catch (e: unknown) {
error.value = e instanceof Error ? e.message : 'Ошибка удаления таймера'
}
}
/** Загружает список таймеров из базы данных */
const loadTimers = async (): Promise<void> => {
loading.value = true
error.value = null
@@ -42,17 +92,31 @@ export default defineComponent({
}
}
/** Переключает раскрытие карточки таймера */
const toggleExpand = (id: number): void => {
expandedId.value = expandedId.value === id ? null : id
}
/** Загружает таймеры при первом рендере */
onMounted(loadTimers)
/** Перезагружает список при возврате на страницу /timers */
watch(
() => route.fullPath,
(path) => {
if (path === '/timers') {
loadTimers()
}
},
{ immediate: false }
)
return {
timers,
loading,
error,
expandedId,
confirmDeleteId,
toggleExpand,
@@ -62,7 +126,13 @@ export default defineComponent({
playCircleOutline,
pencilOutline,
trashOutline,
goToTimer
addCircleOutline,
goToTimer,
goToCreateTimer,
goToEditTimer,
askDeleteTimer,
cancelDeleteTimer,
removeTimer,
}
},
})