profileShare

rasmusjy / splitapp-frontend-vue

Read-only snapshot

No repository description.

main default branch 96 files Expires Sep 13, 2026, 9:06 AM
IndexView.vue 4,343 bytes
1 <script setup lang="ts">
2 import { ref, onMounted } from 'vue'
3 import { useRouter, useRoute } from 'vue-router'
4 import { useI18n } from 'vue-i18n'
5 import PollService from '@/services/PollService'
6 import type { IPoll } from '@/types/IPoll'
7
8 const router = useRouter()
9 const route = useRoute()
10 const { t } = useI18n()
11
12 const polls = ref<IPoll[]>([])
13 const isLoading = ref(true)
14 const error = ref<string | null>(null)
15
16 const tripId = route.params.tripId as string
17
18 onMounted(async () => {
19 const result = await PollService.getByTrip(tripId)
20 if (result.data) {
21 polls.value = result.data
22 } else if (result.errors) {
23 error.value = result.errors.join(', ')
24 }
25 isLoading.value = false
26 })
27 </script>
28
29 <template>
30 <div>
31 <!-- Gradient Header -->
32 <div class="sa-gradient-header sa-gradient-header-blue">
33 <div class="d-flex justify-content-between align-items-center">
34 <div>
35 <h2 class="mb-0">{{ t('polls.index.title') }}</h2>
36 </div>
37 <button
38 class="sa-btn sa-btn-primary sa-btn-sm sa-btn-pill"
39 @click="router.push({ name: 'PollsCreate', params: { tripId } })"
40 >
41 <i class="bi bi-plus-lg"></i> {{ t('polls.index.newPoll') }}
42 </button>
43 </div>
44 </div>
45
46 <!-- Error -->
47 <div v-if="error" class="sa-card-static sa-card-accent sa-card-accent-danger mb-4">
48 <div class="sa-card-body">
49 <i class="bi bi-exclamation-triangle me-2"></i>{{ error }}
50 </div>
51 </div>
52
53 <!-- Loading -->
54 <div v-if="isLoading" class="text-center py-5">
55 <div class="spinner-border text-secondary" role="status"></div>
56 </div>
57
58 <!-- Empty State -->
59 <div v-else-if="polls.length === 0" class="sa-empty">
60 <div class="sa-empty-icon"><i class="bi bi-clipboard2-check"></i></div>
61 <div class="sa-empty-title">{{ t('polls.index.emptyTitle') }}</div>
62 <div class="sa-empty-text">{{ t('polls.index.emptyText') }}</div>
63 <button
64 class="sa-btn sa-btn-primary sa-btn-pill"
65 @click="router.push({ name: 'PollsCreate', params: { tripId } })"
66 >
67 <i class="bi bi-plus-lg"></i> {{ t('polls.index.createFirst') }}
68 </button>
69 </div>
70
71 <!-- Poll Cards -->
72 <div v-else class="d-flex flex-column gap-3">
73 <div
74 v-for="poll in polls"
75 :key="poll.id"
76 class="sa-card"
77 style="cursor: pointer"
78 @click="router.push({ name: 'PollDetail', params: { tripId, id: poll.id } })"
79 >
80 <div class="sa-card-body">
81 <div class="d-flex justify-content-between align-items-start">
82 <div class="flex-grow-1">
83 <div class="fw-bold mb-2" style="font-size: 1.05rem">{{ poll.question }}</div>
84 <div class="d-flex align-items-center gap-3 flex-wrap">
85 <!-- Status -->
86 <span class="d-inline-flex align-items-center gap-1">
87 <span
88 class="sa-status-dot"
89 :class="poll.closedAt ? 'sa-status-dot-pending' : 'sa-status-dot-active sa-status-dot-pulse'"
90 ></span>
91 <span style="font-size: 0.85rem; font-weight: 500" :style="{ color: poll.closedAt ? 'var(--sa-gray-500)' : 'var(--sa-success)' }">
92 {{ poll.closedAt ? t('polls.status.Closed') : t('polls.status.Open') }}
93 </span>
94 </span>
95 <!-- Option count -->
96 <span class="sa-badge sa-badge-neutral">
97 <i class="bi bi-list-ul"></i> {{ t('polls.index.optionCount', { n: poll.options?.length ?? 0 }) }}
98 </span>
99 <!-- Multiple votes badge -->
100 <span v-if="poll.allowMultipleVotes" class="sa-badge sa-badge-info">
101 <i class="bi bi-check2-all"></i> {{ t('polls.index.multipleVotes') }}
102 </span>
103 </div>
104 </div>
105 <button
106 class="sa-btn sa-btn-sm"
107 :class="poll.closedAt ? 'sa-btn-ghost' : 'sa-btn-secondary'"
108 @click.stop="router.push({ name: 'PollDetail', params: { tripId, id: poll.id } })"
109 >
110 {{ poll.closedAt ? t('polls.index.viewResults') : t('polls.index.vote') }}
111 </button>
112 </div>
113 </div>
114 </div>
115 </div>
116 </div>
117 </template>
118