Skip to content

Commit

Permalink
#22 add time tracking board components
Browse files Browse the repository at this point in the history
  • Loading branch information
scmet committed Oct 24, 2024
1 parent b54a3bb commit f50beea
Show file tree
Hide file tree
Showing 5 changed files with 340 additions and 16 deletions.
74 changes: 59 additions & 15 deletions frontend/src/Boards.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@
<v-col cols="12">
<v-text-field v-model="boardName" label="Board Name" required></v-text-field>
</v-col>
<v-col cols="12" v-if="timeTracking">
<!-- Optional year amd hours for time tracking -->
<v-text-field v-model="year" label="Year" required></v-text-field>
<v-text-field v-model="hours" label="Hours" required></v-text-field>
</v-col>
</v-row>
</v-container>
</v-card-text>
Expand Down Expand Up @@ -62,10 +67,18 @@ export default {
return {
boards: [],
boardName: '',
year: '',
hours: '',
dialog: false,
buttonsActivated: false,
};
},
computed: {
// Check if it is user not group route
timeTracking() {
return this.$route.path.includes('/users');
}
},
created() {
this.loadBoards();
},
Expand All @@ -78,10 +91,20 @@ export default {
},
async loadBoards() {
try {
const groupId = this.$route.params.groupId;
const response = await fetch(`${apiUrl}/groups/${groupId}/boards/`);
const groupId = this.$route.params.groupId;
const userId = this.$route.params.userId;
let apiRoute = '';
// Determine whether it's a group or user route
if (this.timeTracking) {
apiRoute = `${apiUrl}/users/${userId}/boards/`;
} else {
apiRoute = `${apiUrl}/groups/${groupId}/boards/`;
}
const response = await fetch(apiRoute);
if (!response.ok) {
throw new Error('Network response was not ok');
throw new Error('Network response was not ok');
}
const data = await response.json();
this.boards = data;
Expand All @@ -90,19 +113,40 @@ export default {
}
},
async addBoard() {
const groupId = this.$route.params.groupId;
try {
const response = await fetch(`${apiUrl}/groups/${groupId}/boards/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name: this.boardName }),
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
const groupId = this.$route.params.groupId;
const userId = this.$route.params.userId;
let apiRoute = '';
// Determine the correct API route for adding a board
if (this.timeTracking) {
apiRoute = `${apiUrl}/users/${userId}/boards/`;
} else {
apiRoute = `${apiUrl}/groups/${groupId}/boards/`;
}
try {
const requestBody = {
name: this.boardName,
};
if (this.timeTracking) {
requestBody.year = this.year;
requestBody.hours = this.hours;
}
const response = await fetch(apiRoute, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name: this.boardName }),
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
this.boardName = "";
this.year = "";
this.hours= "";
this.dialog = false;
this.loadBoards();
} catch (error) {
Expand Down
124 changes: 124 additions & 0 deletions frontend/src/TimeTrackingBoard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<template>
<v-app>
<NavDrawer />
<v-container class="d-flex justify-center my-container" style="background-color: #80BA27;" w-auto>
<h1>{{ boardName }}</h1>
</v-container>
<v-main>
<v-container fluid class="flex mt-4">
<v-btn color="primary" style="margin-left: auto;" dark @click="showDialog">
<v-icon>mdi-plus</v-icon>
Add Card
</v-btn>
<addTimeCard ref="addTimeCard"
@added="(month) => reloadCardsByMonth(month)" />
<!-- Expansion Panels for each Quarter -->
<v-expansion-panels style="padding-top: 20px;">
<v-expansion-panel v-for="(months, index) in quarters" :key="index" class="mb-4" >
<v-expansion-panel-title>
Q{{ index + 1 }} ({{ months.join(' - ') }})
</v-expansion-panel-title>
<v-expansion-panel-text>
<v-card class="mt-4" style="background: #f7f2f9;">
<v-row justify="center">
<Label
v-for="month in months"
:key="month"
ref="monthLabel"
:sectionTitle="month.charAt(0).toUpperCase() + month.slice(1)"
:status="month"
:items="monthItems[month]"
@update:items="monthItems[month] = $event"
@cardMoved="handleCardMoved"
/>
</v-row>
</v-card>
</v-expansion-panel-text>
</v-expansion-panel>
</v-expansion-panels>
</v-container>
</v-main>
</v-app>
</template>

<script>
import NavDrawer from "@/components/NavDrawer.vue";
import addTimeCard from "@/components/addTimeCard.vue";
import Label from "@/components/Label.vue";
import { ref, onMounted } from 'vue';
import { apiUrl } from '@/lib/getApi.js';
export default {
components: {
Label,
NavDrawer,
addTimeCard,
},
setup() {
const monthItems = {
january: [],
february: [],
march: [],
april: [],
may: [],
june: [],
july:[],
august:[],
september:[],
october:[],
november:[],
december:[],
};
const boardName = ref("Kanban Board");
const quarters = [
['januar', 'february', 'march'],
['april', 'may', 'june'],
['juli', 'august', 'september'],
['october', 'november', 'december'],
];
return {
monthItems,
boardName,
quarters
}
},
methods: {
showDialog() {
this.$refs.addTimeCard.showDialog();
},
reloadCardsByMonth(month) {
switch (month) {
case 'january':
this.$refs.janLabel.loadCards();
break;
case 'working_on':
this.$refs.febLabel.loadCards();
break;
case 'review':
this.$refs.marLabel.loadCards();
break;
case 'done':
this.$refs.junLabel.loadCards();
break;
default:
console.error('Invalid status');
}
},
},
}
</script>


<style>
.my-container {
border: 1px solid rgba(92, 92, 92, 0.881);
border-radius: 5px;
width: auto;
}
</style>
139 changes: 139 additions & 0 deletions frontend/src/components/addTimeCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<template>
<v-dialog v-model="dialog" persistent max-width="600px">
<v-card>
<v-card-title>
<span class="text-h5">Add Time</span>
</v-card-title>
<v-card-text>
<v-container>
<v-row>
<v-col cols="12">
<!-- Date picker -->
<v-date-input v-model="date" label="Date" required></v-date-input>
<!-- start and end time-->
<v-text-field
v-model="start"
label="Start"
required
:rules="[validateTime]"
placeholder="HH:mm"
></v-text-field>
<v-text-field
v-model="end"
label="End"
required
:rules="[validateTime]"
placeholder="HH:mm">
</v-text-field>
<!-- shows calculated time -->
<v-text-field
v-model="calculatedTime"
label="Time"
readonly
:rules="[v => v > 0 && v <= 10|| 'Time must be greater than 0 and less than 10']"></v-text-field>
</v-col>
<!--Optional description-->
<v-col cols="12">
<v-textarea
v-model="cardDescription"
label="Description"
placeholder="Optional description"
></v-textarea>
</v-col>
</v-row>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" text @click="dialog = false">Close</v-btn>
<v-btn color="blue darken-1" text @click="addCard">Save</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>

<script>
import { apiUrl } from '@/lib/getApi.js';
export default {
data() {
return {
dialog: false,
date: null,
//start = end - 90 min
start:new Date(new Date().getTime() - 90 * 60 * 1000).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
//end = current time
end: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
calculatedTime: 1.5,
cardDescription: '',
}
},
watch: {
start: 'updateCalculatedTime',
end: 'updateCalculatedTime',
},
methods: {
async addCard() {
const userId = this.$route.params.userId;
const boardId = this.$route.params.boardId;
const card = {
date: this.date,
start: this.start,
end: this.end,
time: this.calculatedTime,
description: this.cardDescription,
};
try {
const response = await fetch(`${apiUrl}/users/${userId}/boards/${boardId}/cards/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(card),
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
this.resetForm();
this.$emit('addedMonth',this.date.getMonth+1);
this.dialog = false;
} catch (error) {
console.error('There was an error!', error);
}
},
validateTime(value) {
const timeRegex = /^(2[0-3]|[01]?[0-9]):([0-5]?[0-9])$/;
return timeRegex.test(value) || 'Please enter a valid time in the format HH:mm';
},
parseTime(time) {
const [hours, minutes] = time.split(':').map(Number);
const date = new Date();
date.setHours(hours, minutes, 0, 0); // Setzt Stunden und Minuten
return date;
},
updateCalculatedTime() {
const startTime = this.parseTime(this.start);
const endTime = this.parseTime(this.end);
const calculatedTime = (endTime - startTime) / (1000 * 60 * 60); // Berechnung in Stunden
if (calculatedTime > 0) {
this.calculatedTime = calculatedTime.toFixed(2); // Zeit auf zwei Dezimalstellen
} else {
this.calculatedTime = '0.00'; // Setze auf 0, wenn ungültig
}
},
resetForm() {
this.start = new Date(new Date().getTime() - 90 * 60 * 1000).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
this.end = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
this.calculatedTime = 1.5; // Zeit zurücksetzen
this.cardDescription = '';
this.date = null; // Setze das Datum zurück
},
showDialog() {
this.dialog = true;
},
},
}
</script>
7 changes: 6 additions & 1 deletion frontend/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,13 @@ import 'vuetify/styles'
import { createVuetify } from 'vuetify'
import * as components from 'vuetify/components'
import * as directives from 'vuetify/directives'
import {VDateInput, VTimePicker} from "vuetify/labs/components";
const vuetify = createVuetify({
components,
components: {
...components,
VDateInput,
VTimePicker,
},
directives,
})

Expand Down
Loading

0 comments on commit f50beea

Please sign in to comment.