1
.gitignore
vendored
1
.gitignore
vendored
@@ -22,6 +22,7 @@ gen/
|
||||
|
||||
# Database
|
||||
*.db
|
||||
*.db*
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
|
||||
|
||||
73
client/src/composables/useTimer.ts
Normal file
73
client/src/composables/useTimer.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { ref, computed } from 'vue';
|
||||
import { CountdownOptions, CountdownReturn } from '@/types/timerType';
|
||||
|
||||
/** Composable для управления обратным отсчётом времени */
|
||||
export function useCountdown({ seconds, onFinish }: CountdownOptions): CountdownReturn {
|
||||
const totalSeconds = seconds * 60;
|
||||
|
||||
/** Реактивное оставшееся время в секундах */
|
||||
const timeLeft = ref<number>(totalSeconds);
|
||||
|
||||
/** Флаг активного состояния таймера */
|
||||
const isRunning = ref<boolean>(false);
|
||||
|
||||
let interval: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
/** Форматирует оставшееся время в строку ЧЧ:ММ:СС */
|
||||
const formatted = computed<string>(() => {
|
||||
const hours = Math.floor(timeLeft.value / 3600);
|
||||
const mins = Math.floor((timeLeft.value % 3600) / 60);
|
||||
const secs = timeLeft.value % 60;
|
||||
return `${String(hours).padStart(2, '0')}:${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
|
||||
});
|
||||
|
||||
/** Вычисляет процент прошедшего времени от общего */
|
||||
const progress = computed<number>(() => {
|
||||
return ((totalSeconds - timeLeft.value) / totalSeconds) * 100;
|
||||
});
|
||||
|
||||
/** Запускает таймер */
|
||||
const start = (): void => {
|
||||
if (isRunning.value) return
|
||||
isRunning.value = true;
|
||||
|
||||
interval = setInterval(() => {
|
||||
if (timeLeft.value > 0) {
|
||||
timeLeft.value--;
|
||||
} else {
|
||||
stop();
|
||||
onFinish?.();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
/** Останавливает таймер и сбрасывает время до начального */
|
||||
const stop = (): void => {
|
||||
if (interval) {
|
||||
clearInterval(interval);
|
||||
interval = null;
|
||||
}
|
||||
isRunning.value = false;
|
||||
timeLeft.value = totalSeconds;
|
||||
}
|
||||
|
||||
/** Приостанавливает таймер без сброса оставшегося времени */
|
||||
const pause = (): void => {
|
||||
if (!isRunning.value) return
|
||||
if (interval) {
|
||||
clearInterval(interval);
|
||||
interval = null;
|
||||
}
|
||||
isRunning.value = false;
|
||||
}
|
||||
|
||||
return {
|
||||
timeLeft,
|
||||
isRunning,
|
||||
formatted,
|
||||
progress,
|
||||
start,
|
||||
stop,
|
||||
pause
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getDb } from "@/db/initDb";
|
||||
import { SELECT_TIMERS } from "@/db/dml/timerDML";
|
||||
import { TimersRow } from "@/types/timerType"
|
||||
import { SELECT_TIMERS, SELECT_TIMER } from "@/db/dml/timerDML";
|
||||
import { TimersRow, TimerRow } from "@/types/timerType";
|
||||
|
||||
|
||||
/** Получает список таймеров */
|
||||
@@ -8,3 +8,9 @@ export async function getTimers(): Promise<TimersRow[]> {
|
||||
const db = await getDb();
|
||||
return await db.select<TimersRow[]>(SELECT_TIMERS);
|
||||
}
|
||||
|
||||
/* Получает таймер по его id **/
|
||||
export async function getTimer(TimerId: number): Promise<TimerRow> {
|
||||
const db = await getDb();
|
||||
return await db.select<TimerRow>(SELECT_TIMER, [TimerId]);
|
||||
}
|
||||
|
||||
@@ -4,3 +4,4 @@ export const INSERT_SEED_DB = `
|
||||
INSERT INTO timer (title, pomodoro_time, break_time, break_long_time, count_pomodoro) VALUES
|
||||
('Timer mini example', 10, 3, 15, 2), ('Timer max example', 90, 10, 40, 8)
|
||||
`
|
||||
export const SELECT_TIMER = 'SELECT * FROM timer WHERE id = ?'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createRouter, createWebHistory } from '@ionic/vue-router';
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
import TimersList from '@/views/TimersList/TimersList.vue';
|
||||
import Timer from '@/views/Timer/Timer.vue';
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
@@ -11,6 +12,11 @@ const routes: RouteRecordRaw[] = [
|
||||
path: '/timers',
|
||||
name: 'Timers',
|
||||
component: TimersList
|
||||
},
|
||||
{
|
||||
path: '/timer/:id',
|
||||
name: 'Timer',
|
||||
component: Timer
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import type { Ref, ComputedRef } from 'vue';
|
||||
|
||||
/** Строка списка таймеров с основными полями */
|
||||
export type TimersRow = {
|
||||
id: number;
|
||||
title: string;
|
||||
@@ -5,4 +8,32 @@ export type TimersRow = {
|
||||
count_pomodoro: number;
|
||||
};
|
||||
|
||||
/** Тип для хранения результата COUNT-запроса */
|
||||
export type CountRow = { cnt: number };
|
||||
|
||||
/** Полная строка таймера со всеми временными параметрами */
|
||||
export type TimerRow = {
|
||||
id: number;
|
||||
title: string;
|
||||
pomodoro_time: number;
|
||||
break_time: number;
|
||||
break_long_time: number;
|
||||
count_pomodoro: number;
|
||||
}
|
||||
|
||||
/** Параметры инициализации обратного отсчёта */
|
||||
export type CountdownOptions = {
|
||||
seconds: number;
|
||||
onFinish?: () => void;
|
||||
}
|
||||
|
||||
/** Возвращаемые значения и методы composable обратного отсчёта */
|
||||
export type CountdownReturn = {
|
||||
timeLeft: Ref<number>;
|
||||
isRunning: Ref<boolean>;
|
||||
formatted: ComputedRef<string>; // ЧЧ:ММ:СС
|
||||
progress: Ref<number>;
|
||||
start: () => void;
|
||||
stop: () => void;
|
||||
pause: () => void;
|
||||
}
|
||||
|
||||
173
client/src/views/Timer/Timer.css
Normal file
173
client/src/views/Timer/Timer.css
Normal file
@@ -0,0 +1,173 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.timer-page-container {
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
||||
'Helvetica Neue', Arial, sans-serif;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.timer-screen {
|
||||
padding: 24px 16px;
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.timer-display-card {
|
||||
background-color: #ffffff;
|
||||
border-radius: 16px;
|
||||
padding: 28px 24px 24px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.timer-card-labels {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.label-left,
|
||||
.label-right {
|
||||
font-size: 13px;
|
||||
color: #9e9e9e;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
.timer-time {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.time-text {
|
||||
font-size: 56px;
|
||||
font-weight: 400;
|
||||
color: #212121;
|
||||
letter-spacing: 2px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.timer-progress-bar {
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background-color: #e0e0e0;
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.timer-progress-fill {
|
||||
height: 100%;
|
||||
background-color: #1976d2;
|
||||
border-radius: 2px;
|
||||
transition: width 1s linear;
|
||||
}
|
||||
|
||||
.timer-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.btn-timer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
width: 120px;
|
||||
height: 44px;
|
||||
flex-shrink: 0; /* запрет на сжатие */
|
||||
background-color: transparent;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
border-radius: 50px;
|
||||
cursor: pointer;
|
||||
letter-spacing: 0.5px;
|
||||
border: 2px solid;
|
||||
}
|
||||
|
||||
.btn-timer ion-icon {
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.btn-timer-start {
|
||||
border-color: #4caf50;
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
.btn-timer-start:hover {
|
||||
background-color: rgba(76, 175, 80, 0.10);
|
||||
}
|
||||
|
||||
.btn-timer-start:active {
|
||||
background-color: rgba(76, 175, 80, 0.22);
|
||||
}
|
||||
|
||||
.btn-timer-stop {
|
||||
border-color: #f44336;
|
||||
color: #f44336;
|
||||
}
|
||||
|
||||
.btn-timer-stop:hover {
|
||||
background-color: rgba(244, 67, 54, 0.10);
|
||||
}
|
||||
|
||||
.btn-timer-stop:active {
|
||||
background-color: rgba(244, 67, 54, 0.22);
|
||||
}
|
||||
|
||||
.btn-timer-pause {
|
||||
border-color: #ffc107;
|
||||
color: #f9a825;
|
||||
}
|
||||
|
||||
.btn-timer-pause:hover {
|
||||
background-color: rgba(255, 193, 7, 0.10);
|
||||
}
|
||||
|
||||
.btn-timer-pause:active {
|
||||
background-color: rgba(255, 193, 7, 0.22);
|
||||
}
|
||||
|
||||
.btn-timer {
|
||||
transition: all 0.2s ease, opacity 0.25s ease, transform 0.25s ease;
|
||||
}
|
||||
|
||||
.btn-timer.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.timer-display-card {
|
||||
padding: 20px 16px;
|
||||
border-radius: 12px;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.time-text {
|
||||
font-size: 44px;
|
||||
}
|
||||
|
||||
.btn-timer {
|
||||
padding: 8px 16px;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
70
client/src/views/Timer/Timer.html
Normal file
70
client/src/views/Timer/Timer.html
Normal file
@@ -0,0 +1,70 @@
|
||||
<ion-page>
|
||||
<ion-content>
|
||||
<div class="timer-page-container">
|
||||
<div class="timer-screen">
|
||||
|
||||
<!-- Timer Card -->
|
||||
<div class="timer-display-card">
|
||||
|
||||
<!-- Card top labels -->
|
||||
<div class="timer-card-labels">
|
||||
<span v-if="timer" class="label-left">{{ timer.title }}</span>
|
||||
<span class="label-right">Помидор</span>
|
||||
</div>
|
||||
|
||||
<!-- Time display -->
|
||||
<div class="timer-time">
|
||||
<span class="time-text">{{ countdown?.formatted }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Progress bar -->
|
||||
<div class="timer-progress-bar">
|
||||
<div class="timer-progress-fill" :style="{ width: `${countdown?.progress.value}%` }"></div>
|
||||
</div>
|
||||
|
||||
<!-- Buttons -->
|
||||
<div class="timer-controls">
|
||||
|
||||
<!-- Видна только при running и paused -->
|
||||
<button
|
||||
class="btn-timer
|
||||
btn-timer-stop"
|
||||
id="btn-stop"
|
||||
v-show="state === 'running' || state === 'paused'"
|
||||
@click="() => { setState('idle'); countdown?.stop(); }"
|
||||
>
|
||||
<ion-icon :icon="stopOutline"></ion-icon>
|
||||
Стоп
|
||||
</button>
|
||||
|
||||
<!-- Видна при idle и paused -->
|
||||
<button
|
||||
class="btn-timer
|
||||
btn-timer-start"
|
||||
id="btn-start"
|
||||
v-show="state === 'idle' || state === 'paused'"
|
||||
@click="() => { setState('running'); countdown?.start(); }"
|
||||
>
|
||||
<ion-icon :icon="playOutline"></ion-icon>
|
||||
Старт
|
||||
</button>
|
||||
|
||||
<!-- Видна только при running -->
|
||||
<button
|
||||
class="btn-timer
|
||||
btn-timer-pause"
|
||||
id="btn-pause"
|
||||
v-show="state === 'running'"
|
||||
@click="() => { setState('paused'); countdown?.pause(); }"
|
||||
>
|
||||
<ion-icon :icon="pauseOutline"></ion-icon>
|
||||
Пауза
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
82
client/src/views/Timer/Timer.ts
Normal file
82
client/src/views/Timer/Timer.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { defineComponent, onMounted, onUnmounted, ref, shallowRef } from 'vue';
|
||||
import { IonIcon, IonPage, IonContent } from '@ionic/vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import {
|
||||
stopOutline,
|
||||
playOutline,
|
||||
pauseOutline
|
||||
} from 'ionicons/icons';
|
||||
|
||||
import { getTimer } from '@/db/crud/timerCrud';
|
||||
import type { TimerRow } from '@/types/timerType';
|
||||
import { useCountdown } from '@/composables/useTimer';
|
||||
import { CountdownReturn } from '@/types/timerType';
|
||||
|
||||
/** Возможные состояния таймера */
|
||||
type TimerState = 'idle' | 'running' | 'paused';
|
||||
|
||||
/** Компонент страницы таймера с управлением обратным отсчётом */
|
||||
export default defineComponent({
|
||||
name: 'Timer',
|
||||
components: { IonIcon, IonPage, IonContent },
|
||||
|
||||
/** Инициализирует данные и логику компонента таймера */
|
||||
setup() {
|
||||
const route = useRoute();
|
||||
const id = Number(route.params.id);
|
||||
|
||||
/** Реактивные данные загруженного таймера */
|
||||
const timer = ref<TimerRow>();
|
||||
|
||||
/** Сообщение об ошибке при загрузке */
|
||||
const error = ref<string | null>(null);
|
||||
|
||||
/** Текущее состояние кнопок управления */
|
||||
const state = ref<TimerState>('idle');
|
||||
|
||||
/** Обновляет текущее состояние таймера */
|
||||
const setState = (newState: TimerState) => {
|
||||
state.value = newState;
|
||||
};
|
||||
|
||||
/** Shallow-ссылка на экземпляр composable обратного отсчёта */
|
||||
const countdown = shallowRef<CountdownReturn | null>(null);
|
||||
|
||||
/** Останавливает таймер при размонтировании компонента */
|
||||
onUnmounted(() => {
|
||||
countdown.value?.stop();
|
||||
});
|
||||
|
||||
/** Загружает данные таймера из БД и инициализирует отсчёт */
|
||||
const loadTimer = async (): Promise<void> => {
|
||||
try {
|
||||
const result = await getTimer(id);
|
||||
const timerData = Array.isArray(result) ? result[0] : result;
|
||||
timer.value = timerData;
|
||||
|
||||
countdown.value = useCountdown({
|
||||
seconds: timerData.pomodoro_time ?? 0,
|
||||
onFinish: () => alert('Время вышло!')
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
error.value = 'Ошибка загрузки таймера';
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(loadTimer);
|
||||
|
||||
return {
|
||||
timer,
|
||||
error,
|
||||
state,
|
||||
setState,
|
||||
stopOutline,
|
||||
playOutline,
|
||||
pauseOutline,
|
||||
countdown
|
||||
};
|
||||
}
|
||||
});
|
||||
4
client/src/views/Timer/Timer.vue
Normal file
4
client/src/views/Timer/Timer.vue
Normal file
@@ -0,0 +1,4 @@
|
||||
<!-- Timer.vue -->
|
||||
<template src="@/views/Timer/Timer.html"></template>
|
||||
<script lang="ts" src="@/views/Timer/Timer.ts"></script>
|
||||
<style src="@/views/Timer/Timer.css" scoped></style>
|
||||
@@ -1,81 +1,85 @@
|
||||
<div class="timers-container">
|
||||
<div class="page-header">
|
||||
<h1>Таймеры</h1>
|
||||
</div>
|
||||
<ion-page>
|
||||
<ion-content>
|
||||
<div class="timers-container">
|
||||
<div class="page-header">
|
||||
<h1>Таймеры</h1>
|
||||
</div>
|
||||
|
||||
<div class="timers-list">
|
||||
<!-- Loading state -->
|
||||
<div v-if="loading" class="loading">
|
||||
<p>Загрузка таймеров...</p>
|
||||
</div>
|
||||
|
||||
<!-- Error state -->
|
||||
<div v-else-if="error" class="error-message">
|
||||
<p>{{ error }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Empty state -->
|
||||
<div v-else-if="timers.length === 0" class="empty-state">
|
||||
<p>Нет таймеров</p>
|
||||
</div>
|
||||
|
||||
<!-- Timers list -->
|
||||
<div v-else class="timer-cards">
|
||||
<div
|
||||
v-for="timer in timers"
|
||||
:key="timer.id"
|
||||
class="timer-card"
|
||||
:class="{ expanded: expandedId === timer.id }"
|
||||
>
|
||||
<!-- Card header -->
|
||||
<div class="card-header" @click="toggleExpand(timer.id)">
|
||||
<div class="timer-icon">
|
||||
<ion-icon :icon="timerOutline"></ion-icon>
|
||||
</div>
|
||||
<div class="timer-info">
|
||||
<h3 class="timer-title">{{ timer.title }}</h3>
|
||||
<p class="timer-subtitle">{{ timer.count_pomodoro }} помидоров по {{ timer.pomodoro_time }} минут</p>
|
||||
</div>
|
||||
<div class="expand-icon">
|
||||
<ion-icon
|
||||
:icon="expandedId === timer.id ? chevronUp : chevronDown"
|
||||
></ion-icon>
|
||||
</div>
|
||||
<div class="timers-list">
|
||||
<!-- Loading state -->
|
||||
<div v-if="loading" class="loading">
|
||||
<p>Загрузка таймеров...</p>
|
||||
</div>
|
||||
|
||||
<!-- Card content -->
|
||||
<div v-show="expandedId === timer.id" class="card-content">
|
||||
<div class="timer-details">
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">Название:</span>
|
||||
<span class="detail-value">{{ timer.title }}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">Длительность помидора:</span>
|
||||
<span class="detail-value">{{ timer.pomodoro_time }} минут</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">Количество помидоров:</span>
|
||||
<span class="detail-value">{{ timer.count_pomodoro }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Error state -->
|
||||
<div v-else-if="error" class="error-message">
|
||||
<p>{{ error }}</p>
|
||||
</div>
|
||||
|
||||
<div class="card-actions">
|
||||
<button class="btn btn-primary">
|
||||
<ion-icon :icon="playCircleOutline"></ion-icon>
|
||||
Открыть
|
||||
</button>
|
||||
<button class="btn btn-secondary">
|
||||
<ion-icon :icon="pencilOutline"></ion-icon>
|
||||
Редактировать
|
||||
</button>
|
||||
<button class="btn btn-danger">
|
||||
<ion-icon :icon="trashOutline"></ion-icon>
|
||||
Удалить
|
||||
</button>
|
||||
<!-- Empty state -->
|
||||
<div v-else-if="timers.length === 0" class="empty-state">
|
||||
<p>Нет таймеров</p>
|
||||
</div>
|
||||
|
||||
<!-- Timers list -->
|
||||
<div v-else class="timer-cards">
|
||||
<div
|
||||
v-for="timer in timers"
|
||||
:key="timer.id"
|
||||
class="timer-card"
|
||||
:class="{ expanded: expandedId === timer.id }"
|
||||
>
|
||||
<!-- Card header -->
|
||||
<div class="card-header" @click="toggleExpand(timer.id)">
|
||||
<div class="timer-icon">
|
||||
<ion-icon :icon="timerOutline"></ion-icon>
|
||||
</div>
|
||||
<div class="timer-info">
|
||||
<h3 class="timer-title">{{ timer.title }}</h3>
|
||||
<p class="timer-subtitle">{{ timer.count_pomodoro }} помидоров по {{ timer.pomodoro_time }} минут</p>
|
||||
</div>
|
||||
<div class="expand-icon">
|
||||
<ion-icon
|
||||
:icon="expandedId === timer.id ? chevronUp : chevronDown"
|
||||
></ion-icon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card content -->
|
||||
<div v-show="expandedId === timer.id" class="card-content">
|
||||
<div class="timer-details">
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">Название:</span>
|
||||
<span class="detail-value">{{ timer.title }}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">Длительность помидора:</span>
|
||||
<span class="detail-value">{{ timer.pomodoro_time }} минут</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">Количество помидоров:</span>
|
||||
<span class="detail-value">{{ timer.count_pomodoro }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-actions">
|
||||
<button @click="goToTimer(timer.id)" class="btn btn-secondary">
|
||||
<ion-icon :icon="playCircleOutline"></ion-icon>
|
||||
Открыть
|
||||
</button>
|
||||
<button class="btn btn-secondary">
|
||||
<ion-icon :icon="pencilOutline"></ion-icon>
|
||||
Редактировать
|
||||
</button>
|
||||
<button class="btn btn-danger">
|
||||
<ion-icon :icon="trashOutline"></ion-icon>
|
||||
Удалить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { defineComponent, onMounted, ref } from 'vue'
|
||||
import { IonIcon } from '@ionic/vue'
|
||||
import { IonIcon, IonPage, IonContent } from '@ionic/vue'
|
||||
import { useRouter } from 'vue-router';
|
||||
import {
|
||||
timerOutline,
|
||||
chevronUp,
|
||||
@@ -14,13 +15,19 @@ import type { TimersRow } from '@/types/timerType'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TimersList',
|
||||
components: { IonIcon },
|
||||
components: { IonIcon, IonPage, IonContent },
|
||||
setup() {
|
||||
const timers = ref<TimersRow[]>([])
|
||||
const loading = ref<boolean>(true)
|
||||
const error = ref<string | null>(null)
|
||||
const expandedId = ref<number | null>(null)
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const goToTimer = (id: number) => {
|
||||
router.push(`/timer/${id}`);
|
||||
};
|
||||
|
||||
const loadTimers = async (): Promise<void> => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
@@ -55,6 +62,7 @@ export default defineComponent({
|
||||
playCircleOutline,
|
||||
pencilOutline,
|
||||
trashOutline,
|
||||
goToTimer
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user