Now dedicated to submitting/archiving lottery.

Removed all other features than adding raffles bought, money received
and mapping of wines and winners.
Also now does api calls from within the component, not external api.js.
Better validation and use of toast plugin for user feedback.
This commit is contained in:
2021-02-18 22:42:25 +01:00
parent 3886313351
commit 2734e9a840

View File

@@ -1,471 +1,247 @@
<template> <template>
<div class="page-container"> <div class="page-container">
<h1>Registrering</h1> <h1>Arkiver lotteri</h1>
<br />
<br />
<div class="notification-element">
<div class="label-div">
<label for="notification">Push-melding</label>
<textarea
id="notification"
type="text"
rows="3"
v-model="pushMessage"
placeholder="Push meldingtekst"
/>
<input id="notification-link" type="text" v-model="pushLink" placeholder="Push-click link" />
</div>
</div>
<div class="button-container">
<button class="vin-button" @click="sendPush">Send push</button>
</div>
<hr /> <h2>Registrer lodd kjøpt</h2>
<h2 id="addwine-title">Prelottery</h2>
<ScanToVinmonopolet @wine="wineFromVinmonopoletScan" v-if="showCamera" />
<div class="button-container">
<button
class="vin-button"
@click="showCamera = !showCamera"
>{{ showCamera ? "Skjul camera" : "Legg til vin med camera" }}</button>
<button class="vin-button" @click="addWine">Legg til en vin manuelt</button>
</div>
<div v-if="wines.length > 0" class="edit-container">
<wine v-for="wine in wines" :key="key" :wine="wine">
<div class="edit">
<div class="button-container row">
<button
class="vin-button"
@click="editWine = amIBeingEdited(wine) ? false : wine"
>{{ amIBeingEdited(wine) ? "Lukk" : "Rediger" }}</button>
<button class="red vin-button" @click="deleteWine(wine)">Slett</button>
</div>
<div v-if="amIBeingEdited(wine)" class="wine-edit">
<div class="label-div" v-for="key in Object.keys(wine)" :key="key">
<label>{{ key }}</label>
<input type="text" v-model="wine[key]" :placeholder="key" />
</div>
</div>
</div>
</wine>
</div>
<div class="button-container" v-if="wines.length > 0">
<button class="vin-button" @click="sendWines">Send inn viner</button>
</div>
<hr />
<h2>Lottery</h2>
<h3>Legg til lodd kjøpt</h3>
<div class="colors"> <div class="colors">
<div v-for="color in lotteryColors" :class="color.css + ' colors-box'" :key="color"> <div v-for="color in lotteryColors" :class="color.key + ' colors-box'" :key="color">
<div class="colors-overlay"> <div class="colors-overlay">
<p>{{ color.name }} kjøpt</p> <p>{{ color.name }} kjøpt</p>
<input v-model="color.value" min="0" :placeholder="0" type="number" /> <input v-model.number="color.value" min="0" :placeholder="0" type="number" />
</div> </div>
</div> </div>
<div class="label-div"> <div class="label-div">
<label>Totalt kjøpt for:</label> <label>Penger mottatt vipps:</label>
<input v-model="payed" placeholder="NOK" type="number" :step="price || 1" min="0" /> <input v-model.number="payed" placeholder="NOK" type="number" :step="price || 1" min="0" />
</div> </div>
</div> </div>
<div class="button-container"> <div v-if="wines.length > 0">
<button class="vin-button" @click="submitLottery">Send inn lotteri</button> <h2>Vinneres vin-valg</h2>
<div class="winner-container">
<wine v-for="wine in wines" :key="wine.id" :wine="wine">
<div class="label-div">
<label for="potential-winner-name">Virtuelle vinnere</label>
<select id="potential-winner-name" type="text" placeholder="Navn" v-model="wine.winner">
<option v-for="winner in winners" :value="winner">{{ winner.name }}</option>
</select>
</div> </div>
<h3>Vinnere</h3>
<a class="wine-link" @click="fetchColorsAndWinners()">Refresh data fra virtuelt lotteri</a>
<div class="winner-container" v-if="winners.length > 0">
<wine v-for="winner in winners" :key="winner" :wine="winner.wine">
<div class="winner-element"> <div class="winner-element">
<div class="color-selector"> <div class="color-selector">
<div class="label-div"> <div class="label-div">
<label>Farge vunnet</label> <label>Farge vunnet</label>
</div> </div>
<button <button
class="blue" class="blue"
:class="{ active: winner.color == 'blue' }" :class="{ active: wine.winner.color == 'blue' }"
@click="winner.color = 'blue'" @click="wine.winner.color = 'blue'"
></button> ></button>
<button <button
class="red" class="red"
:class="{ active: winner.color == 'red' }" :class="{ active: wine.winner.color == 'red' }"
@click="winner.color = 'red'" @click="wine.winner.color = 'red'"
></button> ></button>
<button <button
class="green" class="green"
:class="{ active: winner.color == 'green' }" :class="{ active: wine.winner.color == 'green' }"
@click="winner.color = 'green'" @click="wine.winner.color = 'green'"
></button> ></button>
<button <button
class="yellow" class="yellow"
:class="{ active: winner.color == 'yellow' }" :class="{ active: wine.winner.color == 'yellow' }"
@click="winner.color = 'yellow'" @click="wine.winner.color = 'yellow'"
></button> ></button>
</div> </div>
<div class="label-div"> <div class="label-div">
<label for="winner-name">Navn vinner</label> <label for="winner-name">Navn vinner</label>
<input id="winner-name" type="text" placeholder="Navn" v-model="winner.name" /> <input id="winner-name" type="text" placeholder="Navn" v-model="wine.winner.name" />
</div> </div>
</div> </div>
<div class="label-div">
<label for="potential-winner-name">Virtuelle vinnere</label>
<select
id="potential-winner-name"
type="text"
placeholder="Navn"
v-model="winner.potentialWinner"
@change="potentialChange($event, winner)"
>
<option
v-for="fetchedWinner in fetchedWinners"
:value="stringify(fetchedWinner)"
>{{fetchedWinner.name}}</option>
</select>
</div>
</wine> </wine>
<div class="button-container column">
<button class="vin-button" @click="submitLotteryWinners">Send inn vinnere</button>
<button class="vin-button" @click="resetWinnerDataInStorage">Reset local wines</button>
</div> </div>
</div> </div>
<TextToast v-if="showToast" :text="toastText" v-on:closeToast="showToast = false" /> <div v-if="wines.length > 0" class="button-container column">
<button class="vin-button" @click="archiveLottery">Send inn og arkiver</button>
</div>
</div> </div>
</template> </template>
<script> <script>
import eventBus from "@/mixins/EventBus"; import { dateString } from "@/utils";
import { dateString } from '@/utils'
import {
prelottery,
sendLotteryWinners,
sendLottery,
logWines,
wineSchema,
winnersSecure,
attendees
} from "@/api";
import TextToast from "@/ui/TextToast";
import Wine from "@/ui/Wine"; import Wine from "@/ui/Wine";
import ScanToVinmonopolet from "@/ui/ScanToVinmonopolet";
export default { export default {
components: { TextToast, Wine, ScanToVinmonopolet }, components: { Wine },
data() { data() {
return { return {
payed: undefined, payed: undefined,
winners: [],
fetchedWinners: [],
wines: [], wines: [],
pushMessage: "", winners: [],
pushLink: "/", attendees: [],
toastText: undefined,
showToast: false,
showCamera: false,
editWine: false,
lotteryColors: [ lotteryColors: [
{ value: null, name: "Blå", css: "blue" }, { value: 0, name: "Blå", key: "blue" },
{ value: null, name: "Rød", css: "red" }, { value: 0, name: "Rød", key: "red" },
{ value: null, name: "Grønn", css: "green" }, { value: 0, name: "Grønn", key: "green" },
{ value: null, name: "Gul", css: "yellow" } { value: 0, name: "Gul", key: "yellow" }
], ],
price: __PRICE__ price: __PRICE__ || 10
}; };
}, },
created() { created() {
this.fetchAndAddPrelotteryWines().then(this.getWinnerdataFromStorage); this.fetchLotteryWines();
this.fetchLotteryWinners();
window.addEventListener("unload", this.setWinnerdataToStorage); this.fetchLotteryAttendees();
}, },
beforeDestroy() { watch: {
this.setWinnerdataToStorage(); lotteryColors: {
eventBus.$off("tab-change", () => { deep: true,
this.fetchColorsAndWinners(); handler() {
}); this.payed = this.getRaffleValue();
}
}, },
mounted() { payed(val) {
this.fetchColorsAndWinners(); this.$emit("counter", val);
}
eventBus.$on("tab-change", () => {
this.fetchColorsAndWinners();
});
}, },
methods: { methods: {
stringify(json) { wineWithWinnerMapper(wine) {
return JSON.stringify(json); if (wine.winner == undefined) {
}, wine.winner = {
potentialChange(event, winner) { name: undefined,
let data = JSON.parse(event.target.value); color: undefined
winner.name = data.name;
winner.color = data.color;
},
async fetchColorsAndWinners() {
let winners = await winnersSecure();
let _attendees = await attendees();
let colors = {
red: 0,
blue: 0,
green: 0,
yellow: 0
}; };
this.payed = 0;
for (let i = 0; i < _attendees.length; i++) {
let attendee = _attendees[i];
colors.red += attendee.red;
colors.blue += attendee.blue;
colors.green += attendee.green;
colors.yellow += attendee.yellow;
this.payed +=
(attendee.red + attendee.blue + attendee.green + attendee.yellow) *
10;
} }
return wine;
for (let i = 0; i < this.lotteryColors.length; i++) {
let currentColor = this.lotteryColors[i];
switch (currentColor.css) {
case "red":
currentColor.value = colors.red;
break;
case "blue":
currentColor.value = colors.blue;
break;
a;
case "green":
currentColor.value = colors.green;
break;
case "yellow":
currentColor.value = colors.yellow;
break;
}
}
this.fetchedWinners = winners;
}, },
amIBeingEdited(wine) { fetchLotteryWines() {
return this.editWine.id == wine.id && this.editWine.name == wine.name; return fetch("/api/lottery/wines")
}, .then(resp => resp.json())
async fetchAndAddPrelotteryWines() { .then(response => {
const wines = await prelottery(); if (response.success) {
this.wines = response.wines.map(this.wineWithWinnerMapper);
for (let i = 0; i < wines.length; i++) { } else {
let wine = wines[i]; this.$toast.error({
this.winners.push({ title: "Klarte ikke hente viner.",
name: "", description: response.message
color: "",
potentialWinner: "",
wine: {
name: wine.name,
vivinoLink: wine.vivinoLink,
rating: wine.rating,
image: wine.image,
id: wine.id
}
}); });
} }
});
}, },
wineFromVinmonopoletScan(wineResponse) { fetchLotteryWinners() {
if (this.wines.map(wine => wine.name).includes(wineResponse.name)) { return fetch("/api/lottery/winners")
this.toastText = "Vinen er allerede lagt til."; .then(resp => resp.json())
this.showToast = true; .then(response => {
if (response.success) {
this.winners = response.winners;
} else {
this.$toast.error({
title: "Klarte ikke hente vinnere.",
description: response.message
});
}
});
},
fetchLotteryAttendees() {
return fetch("/api/lottery/attendees")
.then(resp => resp.json())
.then(response => {
if (response.success && response.attendees) {
this.attendees = response.attendees;
this.updateLotteryColorsWithAttendees(response.attendees)
} else {
this.$toast.error({
title: "Klarte ikke hente deltakere.",
description: response.message
});
}
});
},
updateLotteryColorsWithAttendees(attendees) {
this.attendees.map(attendee => {
this.lotteryColors.map(color => (color.value += attendee[color.key]));
});
},
getRaffleValue() {
let rafflesBought = 0;
this.lotteryColors.map(color => rafflesBought += Number(color.value));
return rafflesBought * this.price;
},
archiveLottery: async function(event) {
const validation = this.wines.every(wine => {
if (wine.winner.name == undefined || wine.winner.name == "") {
this.$toast.error({
title: `Navn på vinner må defineres for vin: ${wine.name}`
});
return false;
}
if (wine.winner.color == undefined || wine.winner.color == "") {
this.$toast.error({
title: `Farge vunnet må defineres for vin: ${wine.name}`
});
return false;
}
return true;
});
if (validation == false) {
return; return;
} }
this.toastText = "Fant og la til vin:<br>" + wineResponse.name; let rafflesPayload = {};
this.showToast = true; this.lotteryColors.map(el => rafflesPayload.[el.key] = el.value);
this.wines.unshift(wineResponse); let stolen = 0;
}, const payedDiff = this.payed - this.getRaffleValue()
sendPush: async function() { if (payedDiff) {
let _response = await fetch("/subscription/send-notification", { stolen = payedDiff / this.price;
headers: { }
"Content-Type": "application/json"
// 'Content-Type': 'application/x-www-form-urlencoded', const payload = {
}, wines: this.wines,
raffles: rafflesPayload,
stolen: stolen
};
const options = {
method: "POST", method: "POST",
body: JSON.stringify({ message: this.pushMessage, link: this.pushLink }) headers: { "Content-Type": "application/json" },
}); body: JSON.stringify({
let response = await _response.json(); lottery: payload
if (response) { })
alert("Sendt!");
} else {
alert("Noe gikk galt!");
}
},
addWine: async function(event) {
const wine = await wineSchema();
this.editWine = wine;
this.wines.unshift(wine);
},
deleteWine(deletedWine) {
this.wines = this.wines.filter(wine => wine.name != deletedWine.name);
},
sendWines: async function() {
let response = await logWines(this.wines);
if (response.success == true) {
alert("Sendt!");
window.location.reload();
} else {
alert("Noe gikk galt under innsending");
}
},
addWinner: function(event) {
this.winners.push({
name: "",
color: "",
wine: {
name: "",
vivinoLink: "",
rating: ""
}
});
},
submitLottery: async function(event) {
const colors = {
red: this.lotteryColors.filter(c => c.css == "red")[0].value,
green: this.lotteryColors.filter(c => c.css == "green")[0].value,
blue: this.lotteryColors.filter(c => c.css == "blue")[0].value,
yellow: this.lotteryColors.filter(c => c.css == "yellow")[0].value
}; };
let sendObject = { return fetch("/api/lottery/archive", options)
lottery: { .then(resp => resp.json())
date: dateString(new Date()), .then(response => {
...colors if (response.success) {
} this.$toast.info({
}; title: "Lotteriet er sendt inn og arkivert! Du kan nå slette viner, deltakere & vinnere slettes.",
timeout: 10000
if (sendObject.lottery.red == undefined) { });
alert("Rød må defineres");
return;
}
if (sendObject.lottery.green == undefined) {
alert("Grønn må defineres");
return;
}
if (sendObject.lottery.yellow == undefined) {
alert("Gul må defineres");
return;
}
if (sendObject.lottery.blue == undefined) {
alert("Blå må defineres");
return;
}
sendObject.lottery.bought =
parseInt(colors.blue) +
parseInt(colors.red) +
parseInt(colors.green) +
parseInt(colors.yellow);
const stolen = sendObject.lottery.bought - parseInt(this.payed) / 10;
if (isNaN(stolen) || stolen == undefined) {
alert("Betalt må registreres");
return;
}
sendObject.lottery.stolen = stolen;
let response = await sendLottery(sendObject);
if (response == true) {
alert("Sendt!");
window.location.reload();
} else { } else {
alert(response.message || "Noe gikk galt under innsending"); this.$toast.error({
} title: "Noe gikk galt under innsending!",
}, description: response.message
submitLotteryWinners: async function(event) {
let sendObject = {
lottery: {
date: dateString(new Date()),
winners: this.winners
}
}
if (sendObject.lottery.winners.length == 0) {
alert("Det må være med vinnere");
return;
}
for (let i = 0; i < sendObject.lottery.winners.length; i++) {
let currentWinner = sendObject.lottery.winners[i];
if (currentWinner.name == undefined || currentWinner.name == "") {
alert("Navn må defineres");
return;
}
if (currentWinner.color == undefined || currentWinner.color == "") {
alert("Farge må defineres");
return;
}
}
let response = await sendLotteryWinners(sendObject);
if (response == true) {
alert("Sendt!");
window.location.reload();
} else {
alert(response.message || "Noe gikk galt under innsending");
}
},
getWinnerdataFromStorage() {
let localWinners = localStorage.getItem("winners");
if (localWinners && this.winners.length) {
localWinners = JSON.parse(localWinners);
this.winners = this.winners.map(winner => {
const localWinnerMatch = localWinners.filter(
localWinner =>
localWinner.wine.name == winner.wine.name ||
localWinner.wine.id == winner.wine.id
);
if (localWinnerMatch.length > 0) {
winner.name = localWinnerMatch[0].name || winner.name;
winner.color = localWinnerMatch[0].color || winner.name;
}
return winner;
}); });
} }
let localColors = localStorage.getItem("colorValues");
if (localColors) {
localColors = localColors.split(",");
this.lotteryColors.forEach((color, i) => {
const localColorValue = Number(localColors[i]);
color.value = localColorValue == 0 ? null : localColorValue;
}); });
} }
},
setWinnerdataToStorage() {
localStorage.setItem("winners", JSON.stringify(this.winners));
localStorage.setItem(
"colorValues",
this.lotteryColors.map(color => Number(color.value))
);
window.removeEventListener("unload", this.setWinnerdataToStorage);
},
resetWinnerDataInStorage() {
this.winners = [];
this.fetchAndAddPrelotteryWines().then(resp => (this.winners = resp));
this.lotteryColors.map(color => (color.value = null));
window.location.reload();
}
} }
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "../styles/global.scss"; @import "@/styles/global.scss";
@import "../styles/media-queries.scss"; @import "@/styles/media-queries.scss";
select { select {
margin: 0 0 auto; margin: 0 0 auto;
height: 2rem; height: 2rem;
@@ -473,32 +249,6 @@ select {
width: 98%; width: 98%;
padding: 1%; padding: 1%;
} }
h1 {
width: 100%;
text-align: center;
font-family: knowit, Arial;
}
h2 {
width: 100%;
text-align: center;
font-size: 1.6rem;
font-family: knowit, Arial;
}
.wine-link {
color: #333333;
text-decoration: none;
font-weight: bold;
cursor: pointer;
border-bottom: 1px solid $link-color;
}
hr {
width: 90%;
margin: 2rem auto;
color: grey;
}
.button-container { .button-container {
margin-top: 1rem; margin-top: 1rem;
@@ -516,34 +266,18 @@ hr {
width: 100%; width: 100%;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: space-between; justify-content: space-around;
> div {
margin: 1rem;
max-width: 350px;
}
.button-container { .button-container {
width: 100%; width: 100%;
} }
} }
.edit-container {
margin-top: 2rem;
display: flex;
justify-content: center;
flex-direction: row;
flex-wrap: wrap;
> .wine {
margin-right: 1rem;
margin-bottom: 1rem;
}
}
.edit {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.notification-element {
margin-bottom: 2rem;
}
.winner-element { .winner-element {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -556,27 +290,6 @@ hr {
width: 100%; width: 100%;
} }
} }
.wine-element {
align-items: flex-start;
}
.generate-link {
color: #333333;
text-decoration: none;
display: block;
text-align: center;
margin-bottom: 0px;
}
.wine-edit {
width: 100%;
margin-top: 1.5rem;
label {
margin-top: 0.75rem;
margin-bottom: 0;
}
}
.color-selector { .color-selector {
margin-bottom: 0.65rem; margin-bottom: 0.65rem;
@@ -643,7 +356,7 @@ hr {
flex-wrap: wrap; flex-wrap: wrap;
justify-content: center; justify-content: center;
max-width: 1400px; max-width: 1400px;
margin: 3rem auto 1rem; margin: 0 auto;
@include mobile { @include mobile {
margin: 1.8rem auto 0; margin: 1.8rem auto 0;