diff --git a/src/views/ManageGoalView.vue b/src/views/ManageGoalView.vue index 89111da2adca167258778dfe594d564b73693427..14c44badfe752a364a420f17ac4fb022dd3f2f46 100644 --- a/src/views/ManageGoalView.vue +++ b/src/views/ManageGoalView.vue @@ -4,69 +4,94 @@ import { computed, onMounted, ref, watch } from 'vue' import type { Goal } from '@/types/goal' import ProgressBar from '@/components/ProgressBar.vue' import authInterceptor from '@/services/authInterceptor' +import ModalComponent from '@/components/ModalComponent.vue' const router = useRouter() -const selectedDate = ref<string>('') -const minDate = new Date(new Date().setDate(new Date().getDate() + 1)).toISOString().slice(0, 16) +const minDate = new Date(new Date().setDate(new Date().getDate() + 1)).toISOString().slice(0, 10) +const selectedDate = ref<string>(minDate) + + +const modalMessage = ref<string>('') +const modalTitle = ref<string>('') +const errorModalOpen = ref<boolean>(false) +const confirmModalOpen = ref<boolean>(false) + const goalInstance = ref<Goal>({ title: '', saved: 0, - target: 100, + target: 0, description: '', due: '' }) -watch( - () => goalInstance.value.saved, - (newVal) => { - goalInstance.value.saved = Math.max(0, Math.min(goalInstance.value.target, newVal)) +watch(selectedDate, (newDate) => { + goalInstance.value.due = newDate; +}); + +const isEdit = computed(() => router.currentRoute.value.name === 'edit-goal') +const pageTitle = computed(() => (isEdit.value ? 'Rediger sparemål🎨' : 'Nytt sparemål🎨')) +const submitButton = computed(() => (isEdit.value ? 'Oppdater' : 'Opprett')) +const completion = computed(() => (goalInstance.value.saved / goalInstance.value.target) * 100) + +function validateInputs() { + const errors = []; + + goalInstance.value.due = selectedDate.value + 'T23:59:59.999Z'; + + if (!goalInstance.value.title) { + errors.push('Tittel må fylles ut'); + } + if (!goalInstance.value.target ) { + errors.push('Målbeløp må fylles ut'); + } + if (!goalInstance.value.due) { + errors.push('Forfallsdato må fylles ut'); } -) -watch( - () => goalInstance.value.target, - (newVal) => { - goalInstance.value.target = Math.max(Math.max(goalInstance.value.saved, 1), newVal) + if (goalInstance.value.target < 1) { + errors.push('Målbeløp må være større enn 0'); } -) -watch( - () => selectedDate.value, - (newVal) => { - if (newVal < minDate) selectedDate.value = minDate - goalInstance.value.due = newVal + ':00.000Z' + if (goalInstance.value.saved < 0) { + errors.push('Sparebeløp kan ikke være negativt'); } -) -const isEdit = computed(() => router.currentRoute.value.name === 'edit-goal') -const pageTitle = computed(() => (isEdit.value ? 'Rediger sparemål' : 'Nytt sparemål')) -const submitButton = computed(() => (isEdit.value ? 'Oppdater' : 'Opprett')) -const completion = computed(() => (goalInstance.value.saved / goalInstance.value.target) * 100) + if (goalInstance.value.saved > goalInstance.value.target) { + errors.push('Sparebeløp kan ikke være større enn målbeløp'); + } -const isInputValid = computed(() => { - return ( - goalInstance.value.title.length > 0 && - goalInstance.value.title.length <= 20 && - goalInstance.value.description.length <= 280 && - goalInstance.value.target > 0 && - goalInstance.value.due !== '' - ) -}) + return errors; -const submitAction = () => { - if (!isInputValid.value) { - return () => alert('Fyll ut alle feltene') +} +const submitAction = async() => { + const errors = validateInputs(); + if(errors.length > 0) { + const formatErrors = errors.join('\n'); + modalTitle.value = 'Oops! Noe er feil med det du har fylt ut🚨'; + modalMessage.value = formatErrors.replace(/\n/g, "<br>") + errorModalOpen.value = true; + return; } - - if (isEdit.value) { - updateGoal() - } else { - createGoal() + try { + if (isEdit.value) { + updateGoal(); + } else { + createGoal(); + } + } catch (error) { + console.error(error); + modalTitle.value = 'Systemfeil'; + modalMessage.value = 'En feil oppstod under lagring av utfordringen.'; + errorModalOpen.value = true; } } +watch(selectedDate, (newDate) => { + console.log(newDate) +}) + onMounted(async () => { if (isEdit.value) { const goalId = router.currentRoute.value.params.id @@ -75,12 +100,14 @@ onMounted(async () => { await authInterceptor(`/goals/${goalId}`) .then((response) => { goalInstance.value = response.data - selectedDate.value = response.data.due.slice(0, 16) + selectedDate.value = response.data.due.slice(0, 10) }) .catch((error) => { console.error(error) router.push({ name: 'goals' }) }) + } else { + goalInstance.value.due = selectedDate.value } }) @@ -116,6 +143,24 @@ const deleteGoal = () => { console.error(error) }) } + +function cancelCreation() { + if (goalInstance.value.title !== '' || + goalInstance.value.description !== '' || + goalInstance.value.target !== 0 || + selectedDate.value !== '') { + modalTitle.value = 'Du er i ferd med å avbryte redigeringen🚨'; + modalMessage.value = 'Er du sikker på at du vil avbryte?'; + confirmModalOpen.value = true; + } else { + router.push({ name: 'goals' }) + } +} + +const confirmCancel = () => { + router.push({ name: 'goals' }) + confirmModalOpen.value = false; +} </script> <template> @@ -128,7 +173,7 @@ const deleteGoal = () => { </div> <div class="flex flex-col"> - <p class="mx-4">Beskrivelse</p> + <p class="mx-4">Beskrivelse (valgfri)</p> <textarea v-model="goalInstance.description" class="w-80 h-20 no-rezise" @@ -138,18 +183,17 @@ const deleteGoal = () => { <div class="flex flex-col sm:flex-row gap-3"> <div class="flex flex-col"> - <p class="mx-4">Kroner spart...</p> + <p class="mx-4">Kroner spart💸</p> <input v-model="goalInstance.saved" class="w-40 text-right" - min="0" placeholder="Sparebeløp" type="number" /> </div> <div class="flex flex-col"> - <p class="mx-4">Av målbeløp...*</p> + <p class="mx-4">Av målbeløp💯*</p> <input v-model="goalInstance.target" class="w-40 text-right" @@ -160,31 +204,85 @@ const deleteGoal = () => { </div> <ProgressBar :completion="completion" /> - <div class="flex flex-col"> - <p class="mx-4">Forfallsdato*</p> - <input - :min="minDate" - v-model="selectedDate" - placeholder="Forfallsdato" - type="datetime-local" - /> + <div class="flex flex-row gap-4"> + <div class="flex flex-col"> + <p class="mx-4">Forfallsdato*</p> + <input + :min="minDate" + v-model="selectedDate" + placeholder="Forfallsdato" + type="date" + /> + </div> + <div class="flex flex-col"> + <p>Last opp ikon for utfordringen📸</p> + <button class="mt-2 font-bold cursor-pointer transition-transform duration-300 ease-in-out hover:scale-110 hover:opacity-90">💾</button> + </div> </div> <div class="flex flex-row justify-between w-full"> - <button :disabled="!isInputValid" @click="submitAction" v-text="submitButton" /> <button v-if="isEdit" - class="ml-2 bg-button-danger" + class="ml-2 primary danger" @click="deleteGoal" v-text="'Slett'" /> <button v-else - class="ml-2 bg-button-other" - @click="router.push({ name: 'goals' })" + class="ml-2 primary danger" + @click="cancelCreation" v-text="'Avbryt'" /> + <button + class="primary" + @click="submitAction" v-text="submitButton" + /> </div> + <ModalComponent + :title="modalTitle" + :message="modalMessage" + :isModalOpen="errorModalOpen" + @close="errorModalOpen = false" + > + <template v-slot:input> + <div class="flex justify-center items-center"> + <div class="flex flex-col gap-5"> + <button + class="primary" + @click="errorModalOpen = false" + > + Lukk + </button> + </div> + </div> + </template> + </ModalComponent> + + <ModalComponent + :title="modalTitle" + :message="modalMessage" + :isModalOpen="confirmModalOpen" + @close="confirmModalOpen = false" + > + <template v-slot:input> + <div class="flex justify-center items-center"> + <div class="flex flex-col gap-5"> + <button + class="primary" + @click="confirmCancel" + > + Bekreft + </button> + <button + class="primary danger" + @click="confirmModalOpen = false" + > + Avbryt + </button> + </div> + </div> + </template> + </ModalComponent> </div> </div> </template> @@ -193,4 +291,4 @@ const deleteGoal = () => { .no-rezise { resize: none; } -</style> +</style> \ No newline at end of file diff --git a/src/views/UserGoalsView.vue b/src/views/UserGoalsView.vue index 80d893de8e834265f2e81645d5226223fecf60d1..aaba6e667b3168cabf654dab806f659abced84e0 100644 --- a/src/views/UserGoalsView.vue +++ b/src/views/UserGoalsView.vue @@ -60,8 +60,10 @@ const changeOrder = async () => { <template> <div class="flex flex-col gap-5 items-center"> <h1 class="font-bold m-0">Dine sparemål</h1> - <button @click="router.push({ name: 'new-goal' })">Opprett et nytt sparemål</button> - <h2 class="font-thin m-0">Aktive sparemål</h2> + <button + class="primary" + @click="router.push({ name: 'new-goal' })">Opprett et nytt sparemål</button> + <h2 class="font-bold m-0">Aktive sparemål🚀</h2> <p v-if="activeGoals.length === 0">Du har ingen aktive sparemål</p> <draggable v-else @@ -75,20 +77,28 @@ const changeOrder = async () => { :key="index" :class="[ { 'cursor-move shadow-xl -translate-y-2 duration-300': isDraggable }, - { 'border-4 border-green-500': index === 0 } + { 'border-2 border-lime-400': index === 0 }, + { 'border-2 border-slate-200 hover:bg-slate-50': index !== 0 } ]" :goal-instance="element" :is-clickable="!isDraggable" /> </template> </draggable> - <button :disabled="activeGoals.length === 0" @click="changeOrder()"> + <button + class="primary secondary" + :disabled="activeGoals.length === 0" @click="changeOrder()"> {{ isDraggable ? 'Lagre rekkefølge' : 'Endre rekkefølge' }} </button> - <h2 class="font-thin m-0">Fullførte sparemål</h2> - <p v-if="completedGoals.length === 0">Du har ingen fullførte sparemål</p> + <h2 class="font-bold m-0">Fullførte sparemål💯</h2> + <p v-if="completedGoals.length === 0">Du har ingen fullførte sparemål😢</p> <div v-else class="flex flex-row flex-wrap justify-center gap-10"> - <CardGoal v-for="goal in completedGoals" :key="goal.id" :goal-instance="goal" /> + <CardGoal + class="border-2 border-slate-200 hover:bg-slate-50" + v-for="goal in completedGoals" + :key="goal.id" + :goal-instance="goal" + /> </div> <PageControl :current-page="currentPage" @@ -98,4 +108,5 @@ const changeOrder = async () => { </div> </template> -<style scoped></style> +<style scoped> +</style> \ No newline at end of file diff --git a/src/views/ViewGoalView.vue b/src/views/ViewGoalView.vue index a2e9b176009b30935987283ef7482ae1560eec34..68ca0699389b294b385e61c595f725e9888a7b5d 100644 --- a/src/views/ViewGoalView.vue +++ b/src/views/ViewGoalView.vue @@ -73,29 +73,36 @@ const completeGoal = () => { <div class="flex flex-row flex-wrap items-center justify-center gap-10"> <div class="flex flex-col gap-5 max-w-96"> <button - class="w-min" - @click="router.push({ name: 'goals', params: { id: goalInstance.id } })" + class="w-min bg-transparent rounded-lg font-bold left-10 cursor-pointer transition-transform duration-200 ease-in-out hover:scale-110 hover:opacity-100 justify-start" + @click="router.push({ name: 'goals', params: { id: goalInstance.id } })" > - Oversikt + 👈Oversikt </button> + <div - class="flex flex-col justify-center border-4 border-black rounded-3xl align-middle p-5 card-shadow overflow-hidden w-full" + class="flex flex-col justify-center border-2 rounded-3xl align-middle p-5 card-shadow overflow-hidden w-full" > <h2 class="my-0">Sparemål:</h2> <h2 class="font-light"> {{ goalInstance.title }} </h2> - <p class="text-wrap break-words">{{ goalInstance.description }}</p> + <div class="flex flex-row gap-4 justify-center"> + <p class="text-wrap break-words">{{ goalInstance.description }}</p> + <div> + <img + class="w-20 h-20" + src="https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_1280.png" + alt="Profilbilde" + /> + </div> + </div> <br /> <p class="text-center"> Du har spart {{ goalInstance.saved }}kr av {{ goalInstance.target }}kr </p> <ProgressBar :completion="completion" /> - </div> - - <div class="flex flex-row justify-between gap-2 w-full"> - <button + <button class="primary secondary mt-6" v-if="!isCompleted" @click=" router.push({ @@ -106,15 +113,8 @@ const completeGoal = () => { > Rediger </button> - - <button - v-if="!isCompleted" - @click="completeGoal" - v-text="'Marker målet som ferdig'" - /> - <button - class="bg-button-danger hover:bg-button-danger" + class="danger mt-2 rounded-2xl p-1" @click=" authInterceptor .delete(`/goals/${goalInstance.id}`) @@ -124,9 +124,16 @@ const completeGoal = () => { > Slett </button> + <button class="primary mt-4" + v-if="!isCompleted" + @click="completeGoal" + v-text="'Marker målet som ferdig'" + /> + </div> </div> + <InteractiveSpare :png-size="10" :speech="motivation" direction="left" /> + <div> </div> - <InteractiveSpare :png-size="10" :speech="motivation" direction="left" /> </div> </template>