checkpoint
This commit is contained in:
@@ -5,11 +5,13 @@ import { RouterLink, RouterView } from 'vue-router'
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col h-screen">
|
<div class="flex flex-col h-screen">
|
||||||
<header>
|
<header>
|
||||||
<div class="navbar bg-base-100 shadow-sm flex justify-center">
|
<div class="navbar bg-base-200 shadow-sm flex justify-center">
|
||||||
<RouterLink to="/" class="btn btn-ghost text-xl font-mono">teilweise</RouterLink>
|
<RouterLink to="/" class="btn btn-ghost text-xl font-mono">teilweise</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<RouterView />
|
<main class="h-full">
|
||||||
|
<RouterView />
|
||||||
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
@plugin "daisyui" {
|
@plugin "daisyui" {
|
||||||
themes:
|
themes:
|
||||||
bumblebee --default,
|
bumblebee --default,
|
||||||
abyss --prefersdark;
|
|
||||||
root: '#app';
|
root: '#app';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,18 +2,36 @@
|
|||||||
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 { useRoute } from 'vue-router'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const InfoPath = computed(() => '/boards/' + route.params.boardId)
|
||||||
|
const UsersPath = computed(() => '/boards/' + route.params.boardId + '/users')
|
||||||
|
const SpendingsPath = computed(() => '/boards/' + route.params.boardId + '/spendings')
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ul class="menu menu-horizontal bg-base-200 rounded-box">
|
<ul class="menu menu-horizontal bg-base-200 rounded-box">
|
||||||
<li>
|
<li>
|
||||||
<RouterLink to="/liste" class="menu-active"><IconUser /></RouterLink>
|
<RouterLink
|
||||||
|
:to="InfoPath"
|
||||||
|
:class="[{ 'menu-active': route.path == InfoPath || route.path == InfoPath + '/' }]"
|
||||||
|
><IconInfo
|
||||||
|
/></RouterLink>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<RouterLink to="/"><IconStats /></RouterLink>
|
<RouterLink :to="UsersPath" :class="[{ 'menu-active': route.path.startsWith(UsersPath) }]"
|
||||||
|
><IconUser
|
||||||
|
/></RouterLink>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<RouterLink to="/"><IconInfo /></RouterLink>
|
<RouterLink
|
||||||
|
:to="SpendingsPath"
|
||||||
|
:class="[{ 'menu-active': route.path.startsWith(SpendingsPath) }]"
|
||||||
|
><IconStats
|
||||||
|
/></RouterLink>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ const props = defineProps({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ul class="list w-fit min-w-80 bg-base-100 rounded-box shadow-md">
|
<ul class="list w-fit min-w-80 bg-base-100 border border-base-200 rounded-box shadow-md">
|
||||||
<li class="p-4 pb-2 text-xs opacity-60 tracking-wide">{{ props.title }}</li>
|
<li class="p-4 pb-2 text-xs opacity-60 tracking-wide">{{ props.title }}</li>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -1,30 +1,28 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import IconPlus from './icons/IconPlusCircle.vue'
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
name: String,
|
icon: String,
|
||||||
|
title: String,
|
||||||
subtitle: String,
|
subtitle: String,
|
||||||
subtitleClass: String,
|
subtitleClass: String,
|
||||||
})
|
})
|
||||||
const pic_url = computed(() => {
|
const pic_url = computed(() => {
|
||||||
return 'https://ui-avatars.com/api/?name=' + props.name + '&size=256'
|
return 'https://ui-avatars.com/api/?name=' + props.icon + '&size=256'
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<li class="list-row">
|
<li class="list-row">
|
||||||
<div>
|
<div v-if="icon">
|
||||||
<img class="size-10 rounded-box" :src="pic_url" />
|
<img class="size-10 rounded-box" :src="pic_url" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div>{{ props.name }}</div>
|
<div>{{ props.title }}</div>
|
||||||
<div :class="['text-xs uppercase font-semibold font-mono opacity-60', subtitleClass]">
|
<div :class="['text-xs font-semibold font-mono opacity-60', subtitleClass]">
|
||||||
{{ props.subtitle }}
|
{{ props.subtitle }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-square btn-ghost">
|
<slot></slot>
|
||||||
<IconPlus />
|
|
||||||
</button>
|
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import HomeView from '../views/HomeView.vue'
|
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 BoardSpendingsView from '@/views/BoardSpendingsView.vue'
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
@@ -11,8 +13,8 @@ const router = createRouter({
|
|||||||
component: HomeView,
|
component: HomeView,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/liste',
|
path: '/boards/:boardId',
|
||||||
name: 'liste',
|
name: 'boards',
|
||||||
// route level code-splitting
|
// route level code-splitting
|
||||||
// this generates a separate chunk (About.[hash].js) for this route
|
// this generates a separate chunk (About.[hash].js) for this route
|
||||||
// which is lazy-loaded when the route is visited.
|
// which is lazy-loaded when the route is visited.
|
||||||
@@ -20,9 +22,19 @@ const router = createRouter({
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
|
name: 'BoardInfo',
|
||||||
|
component: BoardInfoView,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'users',
|
||||||
name: 'BoardUsers',
|
name: 'BoardUsers',
|
||||||
component: BoardUsersView,
|
component: BoardUsersView,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'spendings',
|
||||||
|
name: 'BoardEntries',
|
||||||
|
component: BoardSpendingsView,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
20
frontend/src/services/Board.ts
Normal file
20
frontend/src/services/Board.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { User } from './User'
|
||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
|
||||||
|
export class Board {
|
||||||
|
name: string
|
||||||
|
users: Array<User>
|
||||||
|
creationDate: Date
|
||||||
|
guid: string
|
||||||
|
|
||||||
|
constructor(name: string) {
|
||||||
|
this.name = name
|
||||||
|
this.users = Array<User>()
|
||||||
|
this.creationDate = new Date()
|
||||||
|
this.guid = uuidv4()
|
||||||
|
}
|
||||||
|
|
||||||
|
addUser(user: User): void {
|
||||||
|
this.users.push(user)
|
||||||
|
}
|
||||||
|
}
|
||||||
18
frontend/src/services/Spending.ts
Normal file
18
frontend/src/services/Spending.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import type { User } from './User'
|
||||||
|
|
||||||
|
export class Spending {
|
||||||
|
name: string
|
||||||
|
amountCt: number
|
||||||
|
creationDate: Date
|
||||||
|
spender: User | undefined
|
||||||
|
|
||||||
|
constructor(name: string, amountCt: number) {
|
||||||
|
this.name = name
|
||||||
|
this.amountCt = amountCt
|
||||||
|
this.creationDate = new Date()
|
||||||
|
}
|
||||||
|
|
||||||
|
setSpender(user: User) {
|
||||||
|
this.spender = user
|
||||||
|
}
|
||||||
|
}
|
||||||
43
frontend/src/services/User.ts
Normal file
43
frontend/src/services/User.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import type { Spending } from './Spending'
|
||||||
|
|
||||||
|
export class User {
|
||||||
|
name: string
|
||||||
|
spendings: Array<Spending>
|
||||||
|
|
||||||
|
constructor(name: string) {
|
||||||
|
this.name = name
|
||||||
|
this.spendings = Array<Spending>()
|
||||||
|
}
|
||||||
|
|
||||||
|
addSpending(spending: Spending): void {
|
||||||
|
spending.setSpender(this)
|
||||||
|
this.spendings.push(spending)
|
||||||
|
}
|
||||||
|
|
||||||
|
getTotalSpending(): number {
|
||||||
|
return this.spendings.map((w) => w.amountCt).reduce((acc, num) => acc + num, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
getInitials(): string {
|
||||||
|
if (!this.name) return ''
|
||||||
|
|
||||||
|
const words = this.name
|
||||||
|
.trim()
|
||||||
|
.split(/\s+/)
|
||||||
|
.filter((word) => word.length > 0)
|
||||||
|
|
||||||
|
if (words.length === 0) return ''
|
||||||
|
|
||||||
|
if (words.length === 1) {
|
||||||
|
const firstWord = words[0]
|
||||||
|
if (!firstWord || firstWord.length === 0) return ''
|
||||||
|
return firstWord.substring(0, 2).toUpperCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstWord = words[0]
|
||||||
|
const secondWord = words[1]
|
||||||
|
if (!firstWord || !secondWord || firstWord.length === 0 || secondWord.length === 0) return ''
|
||||||
|
|
||||||
|
return (firstWord[0]! + secondWord[0]!).toUpperCase()
|
||||||
|
}
|
||||||
|
}
|
||||||
44
frontend/src/stores/boardStore.ts
Normal file
44
frontend/src/stores/boardStore.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import { Board } from '@/services/Board'
|
||||||
|
import { User } from '@/services/User'
|
||||||
|
import { Spending } from '@/services/Spending'
|
||||||
|
|
||||||
|
export const useBoardStore = defineStore('boardStore', () => {
|
||||||
|
const route = useRoute()
|
||||||
|
const boards = new Map<string, Board>()
|
||||||
|
|
||||||
|
function createBoard(name: string): Board {
|
||||||
|
const newBoard = new Board(name)
|
||||||
|
boards.set(newBoard.guid, newBoard)
|
||||||
|
return newBoard
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBoard(guid: string): Board {
|
||||||
|
const board = boards.get(guid)
|
||||||
|
if (board != undefined) {
|
||||||
|
return board
|
||||||
|
} else {
|
||||||
|
const testBoard = new Board('Grill and Chill')
|
||||||
|
const elias = new User('Elias')
|
||||||
|
elias.addSpending(new Spending('Burger', 1230))
|
||||||
|
elias.addSpending(new Spending('Kaffee', 510))
|
||||||
|
testBoard.addUser(elias)
|
||||||
|
const max = new User('Max')
|
||||||
|
max.addSpending(new Spending('Omlett', 1822))
|
||||||
|
max.addSpending(new Spending('Kaffee', 3073))
|
||||||
|
testBoard.addUser(max)
|
||||||
|
return testBoard
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentBoard(): Board {
|
||||||
|
if (typeof route.params.boardId === 'string') {
|
||||||
|
return getBoard(route.params.boardId)
|
||||||
|
} else {
|
||||||
|
return getBoard('')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { createBoard, getBoard, getCurrentBoard }
|
||||||
|
})
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { ref, computed } from 'vue'
|
|
||||||
import { defineStore } from 'pinia'
|
|
||||||
|
|
||||||
export const useCounterStore = defineStore('counter', () => {
|
|
||||||
const count = ref(0)
|
|
||||||
const doubleCount = computed(() => count.value * 2)
|
|
||||||
function increment() {
|
|
||||||
count.value++
|
|
||||||
}
|
|
||||||
|
|
||||||
return { count, doubleCount, increment }
|
|
||||||
})
|
|
||||||
27
frontend/src/views/BoardInfoView.vue
Normal file
27
frontend/src/views/BoardInfoView.vue
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import ItemList from '@/components/ItemList.vue'
|
||||||
|
import ItemListElement from '@/components/ItemListElement.vue'
|
||||||
|
|
||||||
|
import { useBoardStore } from '@/stores/boardStore'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
const store = useBoardStore()
|
||||||
|
const board = ref(store.getCurrentBoard())
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ItemList title="Informationen">
|
||||||
|
<ItemListElement title="Name" :subtitle="board.name" />
|
||||||
|
<ItemListElement title="Guid" :subtitle="board.guid" />
|
||||||
|
<ItemListElement
|
||||||
|
title="Erstellt am"
|
||||||
|
:subtitle="
|
||||||
|
board.creationDate.toLocaleDateString('de-DE', {
|
||||||
|
day: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
year: 'numeric',
|
||||||
|
})
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</ItemList>
|
||||||
|
</template>
|
||||||
30
frontend/src/views/BoardSpendingsView.vue
Normal file
30
frontend/src/views/BoardSpendingsView.vue
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import ItemList from '@/components/ItemList.vue'
|
||||||
|
import ItemListElement from '@/components/ItemListElement.vue'
|
||||||
|
|
||||||
|
import { useBoardStore } from '@/stores/boardStore'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
const store = useBoardStore()
|
||||||
|
const spendings = ref(store.getCurrentBoard().users.flatMap((u) => u.spendings))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ItemList title="Ausgaben">
|
||||||
|
<ItemListElement
|
||||||
|
v-for="spending in spendings"
|
||||||
|
:key="spending.creationDate.getTime()"
|
||||||
|
:icon="spending.spender?.getInitials()"
|
||||||
|
:title="spending.name"
|
||||||
|
:subtitle="
|
||||||
|
(spending.amountCt / 100).toFixed(2) +
|
||||||
|
'€ - ' +
|
||||||
|
spending.creationDate.toLocaleDateString('de-DE', {
|
||||||
|
day: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
year: 'numeric',
|
||||||
|
})
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</ItemList>
|
||||||
|
</template>
|
||||||
@@ -1,12 +1,28 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
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 { ref } from 'vue'
|
||||||
|
|
||||||
|
const store = useBoardStore()
|
||||||
|
const users = ref(store.getCurrentBoard().users)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ItemList title="Mitglieder">
|
<ItemList title="Mitglieder">
|
||||||
<ItemListElement name="Elias" subtitle="7.07€" subtitle-class="text-green-700" />
|
<ItemListElement
|
||||||
<ItemListElement name="Max" subtitle="-16.27€" subtitle-class="text-red-700" />
|
v-for="user in users"
|
||||||
<ItemListElement name="Daniel" subtitle="9.20€" subtitle-class="text-green-700" />
|
:key="user.name"
|
||||||
|
:icon="user.getInitials()"
|
||||||
|
:title="user.name"
|
||||||
|
:subtitle="(user.getTotalSpending() / 100).toFixed(2) + '€'"
|
||||||
|
subtitle-class="text-green-700"
|
||||||
|
>
|
||||||
|
<button class="btn btn-square btn-ghost">
|
||||||
|
<IconPlus />
|
||||||
|
</button>
|
||||||
|
</ItemListElement>
|
||||||
</ItemList>
|
</ItemList>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import CreateNewList from '../components/CreateNewList.vue'
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main class="grid h-full place-content-center">
|
<div class="grid h-full place-content-center">
|
||||||
<CreateNewList></CreateNewList>
|
<CreateNewList></CreateNewList>
|
||||||
</main>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user