Compare commits
2 Commits
0d56d26afa
...
0e6caca9ab
| Author | SHA1 | Date | |
|---|---|---|---|
| 0e6caca9ab | |||
| d81fb834c7 |
84
frontend/src/components/AddSpendingModal.vue
Normal file
84
frontend/src/components/AddSpendingModal.vue
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import IconX from '@/components/icons/IconX.vue'
|
||||||
|
import IconPlus from '@/components/icons/IconPlusCircle.vue'
|
||||||
|
import { useBoardStore } from '@/stores/boardStore'
|
||||||
|
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
import InputField from './InputField.vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
userName: String,
|
||||||
|
})
|
||||||
|
|
||||||
|
const store = useBoardStore()
|
||||||
|
const isOpen = ref(false)
|
||||||
|
const spendingNameInput = ref('')
|
||||||
|
const spendingNameError = computed(() => {
|
||||||
|
if (spendingNameInput.value.trim().length == 0) return 'Required'
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
const spendingAmountInput = ref('')
|
||||||
|
const spendingAmount = computed(() => {
|
||||||
|
return Number(parseFloat(spendingAmountInput.value.trim().replace('€', '')).toFixed(2))
|
||||||
|
})
|
||||||
|
const spendingAmountError = computed(() => {
|
||||||
|
if (spendingAmountInput.value.trim().length == 0) return 'Required'
|
||||||
|
if (isNaN(spendingAmount.value)) return 'Invalid'
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
const forceValidate = ref(false)
|
||||||
|
|
||||||
|
function submit() {
|
||||||
|
forceValidate.value = true
|
||||||
|
if (spendingAmountError.value == '' && spendingNameError.value == '') {
|
||||||
|
store
|
||||||
|
.getCurrentBoard()
|
||||||
|
.addSpendingByUserName(
|
||||||
|
props.userName ?? '',
|
||||||
|
spendingNameInput.value,
|
||||||
|
spendingAmount.value * 100,
|
||||||
|
)
|
||||||
|
spendingNameInput.value = ''
|
||||||
|
spendingAmountInput.value = ''
|
||||||
|
isOpen.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<button class="btn btn-square btn-ghost" @click="isOpen = true">
|
||||||
|
<IconPlus />
|
||||||
|
</button>
|
||||||
|
<dialog class="modal" :class="{ 'modal-open': isOpen }">
|
||||||
|
<div class="modal-box fieldset p-4">
|
||||||
|
<form method="dialog" @submit.prevent="submit">
|
||||||
|
<button
|
||||||
|
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
|
||||||
|
@click="isOpen = false"
|
||||||
|
>
|
||||||
|
<IconX></IconX>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<h3 class="text-lg font-bold">Hinzufügen von Ausgabe für {{ props.userName }}</h3>
|
||||||
|
|
||||||
|
<InputField
|
||||||
|
label="Bezeichnung"
|
||||||
|
v-model:input-text="spendingNameInput"
|
||||||
|
:input-error="spendingNameError"
|
||||||
|
:force-validate="forceValidate"
|
||||||
|
></InputField>
|
||||||
|
<InputField
|
||||||
|
label="Betrag"
|
||||||
|
v-model:input-text="spendingAmountInput"
|
||||||
|
:input-error="spendingAmountError"
|
||||||
|
:force-validate="forceValidate"
|
||||||
|
placeholder="0,00€"
|
||||||
|
></InputField>
|
||||||
|
|
||||||
|
<button class="btn btn-neutral mt-5" type="submit">Hinzufügen</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
</template>
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
import IconUser from '@/components/icons/IconUser.vue'
|
import IconUser from '@/components/icons/IconUser.vue'
|
||||||
import IconInfo from '@/components/icons/IconInfo.vue'
|
import IconInfo from '@/components/icons/IconInfo.vue'
|
||||||
import IconStats from '@/components/icons/IconStats.vue'
|
import IconStats from '@/components/icons/IconStats.vue'
|
||||||
|
import IconGear from './icons/IconGear.vue'
|
||||||
|
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
@@ -10,6 +11,7 @@ const route = useRoute()
|
|||||||
const InfoPath = computed(() => '/boards/' + route.params.boardId)
|
const InfoPath = computed(() => '/boards/' + route.params.boardId)
|
||||||
const UsersPath = computed(() => '/boards/' + route.params.boardId + '/users')
|
const UsersPath = computed(() => '/boards/' + route.params.boardId + '/users')
|
||||||
const SpendingsPath = computed(() => '/boards/' + route.params.boardId + '/spendings')
|
const SpendingsPath = computed(() => '/boards/' + route.params.boardId + '/spendings')
|
||||||
|
const SettingsPath = computed(() => '/boards/' + route.params.boardId + '/settings')
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -18,20 +20,28 @@ const SpendingsPath = computed(() => '/boards/' + route.params.boardId + '/spend
|
|||||||
<RouterLink
|
<RouterLink
|
||||||
:to="InfoPath"
|
:to="InfoPath"
|
||||||
:class="[{ 'menu-active': route.path == InfoPath || route.path == InfoPath + '/' }]"
|
:class="[{ 'menu-active': route.path == InfoPath || route.path == InfoPath + '/' }]"
|
||||||
><IconInfo
|
><IconInfo />
|
||||||
/></RouterLink>
|
</RouterLink>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<RouterLink :to="UsersPath" :class="[{ 'menu-active': route.path.startsWith(UsersPath) }]"
|
<RouterLink :to="UsersPath" :class="[{ 'menu-active': route.path.startsWith(UsersPath) }]"
|
||||||
><IconUser
|
><IconUser />
|
||||||
/></RouterLink>
|
</RouterLink>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<RouterLink
|
<RouterLink
|
||||||
:to="SpendingsPath"
|
:to="SpendingsPath"
|
||||||
:class="[{ 'menu-active': route.path.startsWith(SpendingsPath) }]"
|
:class="[{ 'menu-active': route.path.startsWith(SpendingsPath) }]"
|
||||||
><IconStats
|
><IconStats />
|
||||||
/></RouterLink>
|
</RouterLink>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<RouterLink
|
||||||
|
:to="SettingsPath"
|
||||||
|
:class="[{ 'menu-active': route.path.startsWith(SettingsPath) }]"
|
||||||
|
>
|
||||||
|
<IconGear />
|
||||||
|
</RouterLink>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,11 +1,42 @@
|
|||||||
<script setup lang="ts"></script>
|
<script setup lang="ts">
|
||||||
|
import InputField from './InputField.vue'
|
||||||
|
import { useBoardStore } from '@/stores/boardStore'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
const route = useRouter()
|
||||||
|
const store = useBoardStore()
|
||||||
|
|
||||||
|
const boardNameInput = ref('')
|
||||||
|
const boardNameError = computed(() => {
|
||||||
|
if (boardNameInput.value.trim().length == 0) return 'Required'
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
const forceValidate = ref(false)
|
||||||
|
|
||||||
|
function submit() {
|
||||||
|
forceValidate.value = true
|
||||||
|
if (boardNameError.value == '') {
|
||||||
|
const newBoard = store.createBoard(boardNameInput.value)
|
||||||
|
route.push('/boards/' + newBoard.guid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<fieldset class="fieldset bg-base-200 border-base-300 rounded-box w-xs border p-4">
|
<form class="bg-base-200 border-base-300 rounded-box w-xs border p-4" @submit.prevent="submit">
|
||||||
<legend class="fieldset-legend">Neue Liste Erstellen</legend>
|
<div class="flex flex-col gap-2">
|
||||||
<div class="join">
|
<h3 class="font-bold">Neue Liste Erstellen</h3>
|
||||||
<input type="text" class="input join-item" placeholder="Listenname" />
|
|
||||||
<button class="btn join-item">Erstellen</button>
|
<InputField
|
||||||
|
label=""
|
||||||
|
placeholder="Listenname"
|
||||||
|
v-model:input-text="boardNameInput"
|
||||||
|
:input-error="boardNameError"
|
||||||
|
:force-validate="forceValidate"
|
||||||
|
></InputField>
|
||||||
|
|
||||||
|
<button class="btn btn-neutral mt-5" type="submit">Erstellen</button>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
32
frontend/src/components/InputField.vue
Normal file
32
frontend/src/components/InputField.vue
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
label: String,
|
||||||
|
placeholder: String,
|
||||||
|
inputError: String,
|
||||||
|
forceValidate: Boolean,
|
||||||
|
})
|
||||||
|
const inputText = defineModel('inputText')
|
||||||
|
|
||||||
|
const touched = ref(false)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<label class="label">{{ props.label }}</label>
|
||||||
|
<input
|
||||||
|
:class="[
|
||||||
|
'input w-full',
|
||||||
|
{ 'input-error': (touched || props.forceValidate) && props.inputError !== '' },
|
||||||
|
]"
|
||||||
|
:placeholder="props.placeholder"
|
||||||
|
v-model="inputText"
|
||||||
|
@blur="touched = true"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-if="(touched || props.forceValidate) && props.inputError !== ''"
|
||||||
|
class="text-error text-sm mt-1"
|
||||||
|
>
|
||||||
|
{{ props.inputError }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
17
frontend/src/components/icons/IconGear.vue
Normal file
17
frontend/src/components/icons/IconGear.vue
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
class="size-4"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z"
|
||||||
|
/>
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
12
frontend/src/components/icons/IconX.vue
Normal file
12
frontend/src/components/icons/IconX.vue
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<template>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
class="size-4"
|
||||||
|
>
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
@@ -3,6 +3,7 @@ import HomeView from '../views/HomeView.vue'
|
|||||||
import BoardUsersView from '@/views/BoardUsersView.vue'
|
import BoardUsersView from '@/views/BoardUsersView.vue'
|
||||||
import BoardInfoView from '@/views/BoardInfoView.vue'
|
import BoardInfoView from '@/views/BoardInfoView.vue'
|
||||||
import BoardSpendingsView from '@/views/BoardSpendingsView.vue'
|
import BoardSpendingsView from '@/views/BoardSpendingsView.vue'
|
||||||
|
import BoardSettingsView from '@/views/BoardSettingsView.vue'
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
@@ -35,6 +36,11 @@ const router = createRouter({
|
|||||||
name: 'BoardEntries',
|
name: 'BoardEntries',
|
||||||
component: BoardSpendingsView,
|
component: BoardSpendingsView,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'settings',
|
||||||
|
name: 'BoardSettings',
|
||||||
|
component: BoardSettingsView,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Spending } from './Spending'
|
||||||
import { User } from './User'
|
import { User } from './User'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
|
||||||
@@ -17,4 +18,9 @@ export class Board {
|
|||||||
addUser(user: User): void {
|
addUser(user: User): void {
|
||||||
this.users.push(user)
|
this.users.push(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addSpendingByUserName(userName: string, spendingName: string, amountCt: number) {
|
||||||
|
const user = this.users.filter((u) => u.name === userName)[0]
|
||||||
|
user?.addSpending(new Spending(spendingName, amountCt))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { Spending } from '@/services/Spending'
|
|||||||
export const useBoardStore = defineStore('boardStore', () => {
|
export const useBoardStore = defineStore('boardStore', () => {
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const boards = new Map<string, Board>()
|
const boards = new Map<string, Board>()
|
||||||
|
let testBoard: Board | undefined
|
||||||
|
|
||||||
function createBoard(name: string): Board {
|
function createBoard(name: string): Board {
|
||||||
const newBoard = new Board(name)
|
const newBoard = new Board(name)
|
||||||
@@ -19,15 +20,18 @@ export const useBoardStore = defineStore('boardStore', () => {
|
|||||||
if (board != undefined) {
|
if (board != undefined) {
|
||||||
return board
|
return board
|
||||||
} else {
|
} else {
|
||||||
const testBoard = new Board('Grill and Chill')
|
if (testBoard === undefined) {
|
||||||
const elias = new User('Elias')
|
testBoard = new Board('Grill and Chill')
|
||||||
elias.addSpending(new Spending('Burger', 1230))
|
const elias = new User('Elias')
|
||||||
elias.addSpending(new Spending('Kaffee', 510))
|
elias.addSpending(new Spending('Burger', 1230))
|
||||||
testBoard.addUser(elias)
|
elias.addSpending(new Spending('Kaffee', 510))
|
||||||
const max = new User('Max')
|
testBoard.addUser(elias)
|
||||||
max.addSpending(new Spending('Omlett', 1822))
|
const max = new User('Max')
|
||||||
max.addSpending(new Spending('Kaffee', 3073))
|
max.addSpending(new Spending('Omlett', 1822))
|
||||||
testBoard.addUser(max)
|
max.addSpending(new Spending('Kaffee', 3073))
|
||||||
|
testBoard.addUser(max)
|
||||||
|
return testBoard
|
||||||
|
}
|
||||||
return testBoard
|
return testBoard
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
9
frontend/src/views/BoardSettingsView.vue
Normal file
9
frontend/src/views/BoardSettingsView.vue
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import ItemList from '@/components/ItemList.vue'
|
||||||
|
|
||||||
|
import { useBoardStore } from '@/stores/boardStore'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ItemList title="Einstellungen"> </ItemList>
|
||||||
|
</template>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import AddSpendingModal from '@/components/AddSpendingModal.vue'
|
||||||
import ItemList from '@/components/ItemList.vue'
|
import ItemList from '@/components/ItemList.vue'
|
||||||
import ItemListElement from '@/components/ItemListElement.vue'
|
import ItemListElement from '@/components/ItemListElement.vue'
|
||||||
import IconPlus from '@/components/icons/IconPlusCircle.vue'
|
|
||||||
|
|
||||||
import { useBoardStore } from '@/stores/boardStore'
|
import { useBoardStore } from '@/stores/boardStore'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
@@ -20,9 +20,7 @@ const users = ref(store.getCurrentBoard().users)
|
|||||||
:subtitle="(user.getTotalSpending() / 100).toFixed(2) + '€'"
|
:subtitle="(user.getTotalSpending() / 100).toFixed(2) + '€'"
|
||||||
subtitle-class="text-green-700"
|
subtitle-class="text-green-700"
|
||||||
>
|
>
|
||||||
<button class="btn btn-square btn-ghost">
|
<AddSpendingModal :user-name="user.name" />
|
||||||
<IconPlus />
|
|
||||||
</button>
|
|
||||||
</ItemListElement>
|
</ItemListElement>
|
||||||
</ItemList>
|
</ItemList>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user