EditView.vue
6,065 bytes
| 1 | <script setup lang="ts"> |
|---|---|
| 2 | import { ref, computed, onMounted } from 'vue' |
| 3 | import { useRouter, useRoute } from 'vue-router' |
| 4 | import { useI18n } from 'vue-i18n' |
| 5 | import TripService from '@/services/TripService' |
| 6 | import CurrencyService from '@/services/CurrencyService' |
| 7 | import type { ICurrency } from '@/types/ICurrency' |
| 8 | |
| 9 | const router = useRouter() |
| 10 | const route = useRoute() |
| 11 | const { t } = useI18n() |
| 12 | |
| 13 | const name = ref('') |
| 14 | const description = ref('') |
| 15 | const destination = ref('') |
| 16 | const startDate = ref('') |
| 17 | const endDate = ref('') |
| 18 | const defaultCurrencyId = ref('') |
| 19 | const status = ref('Active') |
| 20 | const currencies = ref<ICurrency[]>([]) |
| 21 | const errors = ref<string[]>([]) |
| 22 | const isSaving = ref(false) |
| 23 | const isLoading = ref(true) |
| 24 | |
| 25 | const dateError = computed<string | null>(() => { |
| 26 | if (!startDate.value || !endDate.value) return null |
| 27 | return endDate.value < startDate.value |
| 28 | ? t('trips.create.dateError') |
| 29 | : null |
| 30 | }) |
| 31 | |
| 32 | const tripId = route.params.tripId as string |
| 33 | |
| 34 | onMounted(async () => { |
| 35 | const [tripResult, currencyResult] = await Promise.all([ |
| 36 | TripService.getById(tripId), |
| 37 | CurrencyService.getAll(), |
| 38 | ]) |
| 39 | |
| 40 | if (currencyResult.data) { |
| 41 | currencies.value = currencyResult.data |
| 42 | } |
| 43 | |
| 44 | if (tripResult.data) { |
| 45 | name.value = tripResult.data.name |
| 46 | description.value = tripResult.data.description ?? '' |
| 47 | destination.value = tripResult.data.destination ?? '' |
| 48 | startDate.value = tripResult.data.startDate?.substring(0, 10) ?? '' |
| 49 | endDate.value = tripResult.data.endDate?.substring(0, 10) ?? '' |
| 50 | defaultCurrencyId.value = tripResult.data.defaultCurrencyId |
| 51 | status.value = tripResult.data.status |
| 52 | } else { |
| 53 | errors.value = tripResult.errors ?? [t('trips.edit.notFound')] |
| 54 | } |
| 55 | isLoading.value = false |
| 56 | }) |
| 57 | |
| 58 | async function handleSubmit() { |
| 59 | if (dateError.value) { |
| 60 | errors.value = [dateError.value] |
| 61 | return |
| 62 | } |
| 63 | errors.value = [] |
| 64 | isSaving.value = true |
| 65 | |
| 66 | const result = await TripService.update(tripId, { |
| 67 | id: tripId, |
| 68 | name: name.value, |
| 69 | description: description.value || null, |
| 70 | destination: destination.value || null, |
| 71 | startDate: startDate.value ? new Date(startDate.value).toISOString() : null, |
| 72 | endDate: endDate.value ? new Date(endDate.value).toISOString() : null, |
| 73 | defaultCurrencyId: defaultCurrencyId.value, |
| 74 | status: status.value, |
| 75 | }) |
| 76 | |
| 77 | if (result.errors) { |
| 78 | errors.value = result.errors |
| 79 | } else { |
| 80 | router.push({ name: 'TripsIndex' }) |
| 81 | } |
| 82 | |
| 83 | isSaving.value = false |
| 84 | } |
| 85 | |
| 86 | async function handleDelete() { |
| 87 | if (!confirm(t('trips.edit.confirmDelete'))) return |
| 88 | const result = await TripService.delete(tripId) |
| 89 | if (result.errors) { |
| 90 | errors.value = result.errors |
| 91 | } else { |
| 92 | router.push({ name: 'TripsIndex' }) |
| 93 | } |
| 94 | } |
| 95 | </script> |
| 96 | |
| 97 | <template> |
| 98 | <div class="row justify-content-center"> |
| 99 | <div v-if="isLoading" class="text-center py-5"> |
| 100 | <div class="spinner-border" role="status"></div> |
| 101 | </div> |
| 102 | |
| 103 | <div v-else class="col-md-8 col-lg-6"> |
| 104 | <h2>{{ t('trips.edit.title') }}</h2> |
| 105 | |
| 106 | <div v-if="errors.length" class="alert alert-danger"> |
| 107 | <div v-for="error in errors" :key="error">{{ error }}</div> |
| 108 | </div> |
| 109 | |
| 110 | <form @submit.prevent="handleSubmit"> |
| 111 | <div class="mb-3"> |
| 112 | <label for="name" class="form-label">{{ t('trips.edit.name') }}</label> |
| 113 | <input id="name" v-model="name" type="text" class="form-control" required /> |
| 114 | </div> |
| 115 | |
| 116 | <div class="mb-3"> |
| 117 | <label for="destination" class="form-label">{{ t('trips.edit.destination') }}</label> |
| 118 | <input id="destination" v-model="destination" type="text" class="form-control" /> |
| 119 | </div> |
| 120 | |
| 121 | <div class="mb-3"> |
| 122 | <label for="description" class="form-label">{{ t('trips.edit.description') }}</label> |
| 123 | <textarea id="description" v-model="description" class="form-control" rows="3"></textarea> |
| 124 | </div> |
| 125 | |
| 126 | <div class="row mb-3"> |
| 127 | <div class="col-6"> |
| 128 | <label for="startDate" class="form-label">{{ t('trips.edit.startDate') }}</label> |
| 129 | <input id="startDate" v-model="startDate" type="date" class="form-control" /> |
| 130 | </div> |
| 131 | <div class="col-6"> |
| 132 | <label for="endDate" class="form-label">{{ t('trips.edit.endDate') }}</label> |
| 133 | <input |
| 134 | id="endDate" |
| 135 | v-model="endDate" |
| 136 | type="date" |
| 137 | class="form-control" |
| 138 | :class="{ 'is-invalid': dateError }" |
| 139 | :min="startDate || undefined" |
| 140 | /> |
| 141 | <div v-if="dateError" class="invalid-feedback">{{ dateError }}</div> |
| 142 | </div> |
| 143 | </div> |
| 144 | |
| 145 | <div class="mb-3"> |
| 146 | <label for="currency" class="form-label">{{ t('trips.edit.currency') }}</label> |
| 147 | <select id="currency" v-model="defaultCurrencyId" class="form-select" required> |
| 148 | <option value="" disabled>{{ t('trips.edit.selectCurrency') }}</option> |
| 149 | <option v-for="c in currencies" :key="c.id" :value="c.id"> |
| 150 | {{ c.code }} — {{ c.name }} ({{ c.symbol }}) |
| 151 | </option> |
| 152 | </select> |
| 153 | </div> |
| 154 | |
| 155 | <div class="mb-3"> |
| 156 | <label for="status" class="form-label">{{ t('trips.edit.status') }}</label> |
| 157 | <select id="status" v-model="status" class="form-select"> |
| 158 | <option value="Active">{{ t('trips.status.Active') }}</option> |
| 159 | <option value="Completed">{{ t('trips.status.Completed') }}</option> |
| 160 | </select> |
| 161 | </div> |
| 162 | |
| 163 | <div class="d-flex gap-2"> |
| 164 | <button type="button" class="btn btn-danger" @click="handleDelete">{{ t('common.delete') }}</button> |
| 165 | <div class="flex-grow-1"></div> |
| 166 | <button |
| 167 | type="button" |
| 168 | class="btn btn-outline-secondary" |
| 169 | @click="router.push({ name: 'TripsIndex' })" |
| 170 | > |
| 171 | {{ t('common.cancel') }} |
| 172 | </button> |
| 173 | <button type="submit" class="btn btn-primary" :disabled="isSaving || !!dateError"> |
| 174 | {{ isSaving ? t('common.saving') : t('common.saveChanges') }} |
| 175 | </button> |
| 176 | </div> |
| 177 | </form> |
| 178 | </div> |
| 179 | </div> |
| 180 | </template> |
| 181 | |