Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Time tracking #11

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
298 changes: 197 additions & 101 deletions frontend/src/components/tracking/timeTrackerLine.vue
Original file line number Diff line number Diff line change
@@ -1,131 +1,227 @@
<template>
<q-card class="q-mx-sm row">
<q-input
v-model="selectedDate"
label="Date"
type="date"
class="q-ma-sm" :readonly="timerRunning" />
<q-input
v-model="timeFrom"
label="From"
type="time" class="q-ma-sm" :readonly="timerRunning" />
<q-input
v-model="timeTo"
label="To"
type="time" class="q-ma-sm" :readonly="timerRunning" />
<q-select
v-model="selectedProject"
:options="projectOptions"
label="Project" class="q-ma-sm" />

<!-- Read only field showing computed duration -->
<q-input
v-model="duration"
label="Duration"
type="text" class="q-ma-sm" readonly />

<q-btn v-if="!timerRunning"
label="Start" color="primary" class="q-ma-sm"
@click="startTimer" />
<q-btn v-if="timerRunning"
label="STOP"
color="negative"
class="q-ma-sm" @click="cancelTimer" />
<q-btn
type="submit"
label="Add"
color="primary"
class="q-ma-sm" @click="submitForm" :disable="!selectedDate || !timeFrom || !timeTo || !selectedProject" />
<q-input v-model="selectedDate" label="Date" type="date" class="q-ma-sm" />
<q-input v-model="timeFrom" label="From" type="time" class="q-ma-sm" />
<q-input v-model="timeTo" label="To" type="time" class="q-ma-sm" />
<q-select v-model="selectedProject" :options="projectOptions" label="Project" class="q-ma-sm" />
<q-input v-model="duration" label="Duration" type="text" class="q-ma-sm" readonly />

<q-btn v-if="!timerRunning" label="Start" color="primary" class="q-ma-sm" @click="startTimer" />
<q-btn v-if="timerRunning" label="Finish" color="negative" class="q-ma-sm" @click="finish" />
<q-btn v-if="timerRunning" label="Discard" color="primary" class="q-ma-sm" @click="stopTimer" />
<q-btn v-if="!timerRunning" type="submit" label="Add" color="primary" class="q-ma-sm" @click="submitForm" :disable="!isValidForm" />
</q-card>
</template>

<script setup lang="ts">
import { ref, onMounted, computed, defineComponent, defineEmits } from 'vue'
import { ref, computed, defineProps, defineEmits, onMounted, onBeforeUnmount, watch } from 'vue'
import { useTimerStore } from 'src/stores/timerStore'
import { TimeTrackingItemNew } from 'src/models'
import { uid } from 'quasar'
import { uid, Notify } from 'quasar'

defineComponent({
name: 'TimeTrackerLine'
const timerStore = useTimerStore()

const projectOptions = ref([
'Anotace obrázků', 'Anotace textů', 'Anotace chodců', 'Vyhledávání obrázků', 'Vyhledávání textů',
'Vyhledávání obličejů', 'Anotace Stránek', 'TextBite'
])

const selectedProject = computed({
get: () => timerStore.selectedProject,
set: value => timerStore.selectedProject = value
})

interface Props {
userId: string | null
}
const selectedDate = computed({
get: () => timerStore.selectedDate,
set: value => timerStore.selectedDate = value
})

const props = defineProps<Props>()
const timeFrom = computed({
get: () => timerStore.timeFrom,
set: value => timerStore.timeFrom = value
})

const timeTo = computed({
get: () => timerStore.timeTo,
set: value => timerStore.timeTo = value
})

const timerRunning = computed(() => timerStore.timerRunning)

const duration = computed(() => {
const timeStart = new Date(`01/01/2007 ${timeFrom.value}`)
const timeEnd = new Date(`01/01/2007 ${timeTo.value}`)
const diff = timeEnd.getTime() - timeStart.getTime()
return `${diff / 1000 / 60} min`
})

const isValidForm = computed(() => selectedDate.value && timeFrom.value && timeTo.value && selectedProject.value)

const props = defineProps<{ userId: string | null }>()
const emit = defineEmits(['addTimeEntry'])

const projectOptions = ref(['Anotace obrázků', 'Anotace textů', 'Anotace chodců', 'Vyhledávání obrázků', 'Vyhledávání textů', 'Vyhledávání obličejů', 'Anotace Stránek', 'TextBite'])
const selectedDate = ref('')
const timeFrom = ref('')
const timeTo = ref('')
const selectedProject = ref(projectOptions.value[0].value)
const timerRunning = ref(false)
let intervalId: any = null

function submitForm () {
timerRunning.value = false
clearInterval(intervalId)
intervalId = null

if (!selectedDate.value || !timeFrom.value || !timeTo.value || !selectedProject.value || !props.userId) {
return
}
const newTimeEntry: TimeTrackingItemNew = {
function createNewTimeEntry(): TimeTrackingItemNew {
return {
id: uid(),
user_id: props.userId,
start_time: selectedDate.value + ' ' + timeFrom.value,
end_time: selectedDate.value + ' ' + timeTo.value,
user_id: props.userId ?? '',
start_time: `${selectedDate.value} ${timeFrom.value}`,
end_time: `${selectedDate.value} ${timeTo.value}`,
task: selectedProject.value,
description: ''
}
}

function addNewTimeEntry(newTimeEntry: TimeTrackingItemNew) {
emit('addTimeEntry', newTimeEntry)
console.log(newTimeEntry)
reset()
}

function reset () {
selectedDate.value = new Date().toISOString().slice(0, 10)
timeFrom.value = new Date().toISOString().slice(11, 16)
timeTo.value = ''
timerRunning.value = false
clearInterval(intervalId)
intervalId = null
}

// Periodicaly update timer when timerRunnning is True
function startTimer () {
selectedDate.value = new Date().toISOString().slice(0, 10)
timeFrom.value = new Date().toISOString().slice(11, 16)
timeTo.value = new Date().toISOString().slice(11, 16)
timerRunning.value = true
intervalId = setInterval(() => {
if (timerRunning.value) {
timeTo.value = new Date().toISOString().slice(11, 16)
}

function storeTimeEntry(newTimeEntry: TimeTrackingItemNew) {
const storedEntries = JSON.parse(localStorage.getItem('tempTimeEntries') || '[]')
storedEntries.push(newTimeEntry)
localStorage.setItem('tempTimeEntries', JSON.stringify(storedEntries))
}

function retrieveAndEmitStoredTimeEntries() {
const storedEntries = JSON.parse(localStorage.getItem('tempTimeEntries') || '[]')
storedEntries.forEach((entry: TimeTrackingItemNew) => {
emit('addTimeEntry', entry)
})
localStorage.removeItem('tempTimeEntries')
}

function saveFormState() {
localStorage.setItem('selectedDate', selectedDate.value)
localStorage.setItem('timeFrom', timeFrom.value)
localStorage.setItem('timeTo', timeTo.value)
localStorage.setItem('selectedProject', selectedProject.value)
}

function resetForm() {
timerStore.selectedDate = new Date().toISOString().slice(0, 10)
timerStore.timeFrom = ''
timerStore.timeTo = ''
timerStore.timerRunning = false
saveFormState()
}

function showNotification() {
timerStore.isNotificationVisible = true
timerStore.notificationRef = Notify.create({
message: `${selectedProject.value} (${timerStore.calculateDuration()} min)`,
color: 'primary',
position: 'bottom-right',
timeout: 0,
actions: [
{
label: 'Finish',
color: 'white',
handler: finishNotification,
style: 'background-color: #ff0000; color: #ffffff; border: 2px solid #ff0000; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); padding: 5px 10px; border-radius: 2px;',
class: 'q-btn'
}
]
})
}

function hideNotification() {
if (timerStore.notificationRef) {
timerStore.notificationRef()
timerStore.notificationRef = null
}
timerStore.isNotificationVisible = false
}

function updateNotification() {
hideNotification()
showNotification()
}

function formatTime(date: Date) {
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false })
}

function updateTimer() {
if (timerRunning.value) {
const newTime = new Date()
timeTo.value = formatTime(newTime)
saveFormState()
if (newTime.getSeconds() === 0) {
updateNotification()
}
}, 1000)
}
}

function startTimer() {
timerStore.startTimer(selectedProject.value)
timerStore.intervalId = setInterval(updateTimer, 1000)
showNotification()
}

function cancelTimer () {
timerRunning.value = false
clearInterval(intervalId)
intervalId = null
function stopTimer() {
if (timerStore.intervalId) {
clearInterval(timerStore.intervalId)
timerStore.intervalId = null
}
hideNotification()
timerStore.stopTimer()
resetForm()
}

function finish() {
timeTo.value = formatTime(new Date())
timerStore.stopTimer()
clearInterval(timerStore.intervalId as unknown as number);
timerStore.intervalId = null
saveFormState()
hideNotification()
addNewTimeEntry(createNewTimeEntry())
resetForm()
}

function finishNotification() {
timeTo.value = formatTime(new Date())
timerStore.stopTimer()
clearInterval(timerStore.intervalId as unknown as number);
timerStore.intervalId = null
saveFormState()
hideNotification()
storeTimeEntry(createNewTimeEntry())
resetForm()
}

function submitForm() {
if (!isValidForm.value) return
addNewTimeEntry(createNewTimeEntry())
hideNotification()
resetForm()
}

onMounted(() => {
// init with today's date (only date without time)
selectedDate.value = new Date().toISOString().slice(0, 10)
selectedProject.value = projectOptions.value[0]
})
selectedDate.value = localStorage.getItem('selectedDate') || ''
timeFrom.value = localStorage.getItem('timeFrom') || ''
timeTo.value = localStorage.getItem('timeTo') || ''
selectedProject.value = localStorage.getItem('selectedProject') || projectOptions.value[0]
if (timerRunning.value) {
timerStore.intervalId = setInterval(updateTimer, 1000)
hideNotification()
showNotification()
}

const duration = computed(() => {
var timeStart = new Date('01/01/2007 ' + timeFrom.value)
var timeEnd = new Date('01/01/2007 ' + timeTo.value)
var diff = timeEnd - timeStart;
return diff / 1000 / 60 + ' min';
retrieveAndEmitStoredTimeEntries()
})

onBeforeUnmount(() => {
retrieveAndEmitStoredTimeEntries()
if (timerStore.intervalId) {
clearInterval(timerStore.intervalId)
timerStore.intervalId = null
}
})

</script>
watch(timerRunning, newVal => {
if (!newVal && timerStore.intervalId) {
clearInterval(timerStore.intervalId)
timerStore.intervalId = null
}
})
</script>
8 changes: 3 additions & 5 deletions frontend/src/components/tracking/timeTrackingItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ defineComponent({

const emit = defineEmits(['delete'])


interface Props {
timeEntry: TimeTrackingItem
}
Expand All @@ -54,12 +53,13 @@ const itemDate = computed(() => {

const timeFrom = computed(() => {
const date = new Date(props.timeEntry.start_time)
return date.toISOString().substr(11, 5)
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false })
})


const timeTo = computed(() => {
const date = new Date(props.timeEntry.end_time)
return date.toISOString().substr(11, 5)
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false })
})

const duration = computed(() => {
Expand All @@ -75,6 +75,4 @@ const task = computed(() => {
return props.timeEntry.task
})



</script>
Loading