Time tracking you can host anywhere, built on Django. Full export support in multiple formats and easily extensible.
djangodockerhandcodedpythonself-hostedtime-trackingtimetracker
1<template>
2<div :id="'entry-' + entry.id" class="entry row py-2 bg-light small">
3 <div v-if="entry.project" class="col-sm-3 client-project">
4 <div class="small">
5 <icon :icon="['fas', 'address-book']" class="mr-2 text-muted"/>
6 {{ getClient(getProject(entry.project).client).name }}
7 </div>
8 <icon :icon="['fas', 'briefcase']" class="mr-1 text-muted"/>
9 {{ getProject(entry.project).name }}
10 </div>
11 <div :class="['d-flex', 'flex-column', 'align-self-end', 'tasks', 'col-sm-5']">
12 <div v-if="entry.task" class="small">
13 <icon :icon="['fas', 'tasks']" class="mr-2 text-muted"/>
14 {{ getTask(entry.task).name }}
15 </div>
16 <div v-if="entry.note" class="entry-note">
17 <icon :icon="['fas', 'comment']" class="mr-1 text-muted"/>
18 {{ entry.note }}
19 </div>
20 </div>
21 <div class="col-sm-2 align-items-center display-4 duration">
22 <icon :icon="['fas', 'clock']" class="mr-2 text-muted small"/>
23 {{ $moment.duration(entry.duration, 'hours').format('h:mm', {trim: false}) }}
24 </div>
25 <div class="col-sm-2 align-self-center datetimes flex-column">
26 <div class="datetime-start small">
27 <icon :icon="['fas', 'hourglass-start']" class="mr-1 text-muted"/>
28 {{ formatDateTime(entry.datetime_start) }}
29 </div>
30 <div class="datetime-end small">
31 <icon :icon="['fas', 'hourglass-end']" class="mr-1 text-muted"/>
32 {{ formatDateTime(entry.datetime_end) }}
33 </div>
34 </div>
35 <div
36 v-if="$perms.change_entry || $perms.delete_entry"
37 class="col-sm-2 d-flex align-self-center justify-content-end">
38 <button
39 id="entry-menu"
40 type="button"
41 data-toggle="dropdown"
42 class="btn btn-faded btn-sm btn-icon dropdown-toggle">
43 <icon :icon="['fas', 'ellipsis-v']"/>
44 </button>
45 <div class="dropdown-menu dropdown-menu-right">
46 <button
47 id="entry-menu-change"
48 class="dropdown-item"
49 v-if="$perms.change_entry"
50 @click.prevent.exact="$emit('modal')">
51 Edit
52 </button>
53 <button
54 id="entry-menu-delete"
55 class="dropdown-item"
56 v-if="$perms.delete_entry"
57 @click.prevent.exact="deleteEntry(entry)">
58 Delete
59 </button>
60 </div>
61 </div>
62</div>
63</template>
64
65
66<script>
67import {mapActions, mapGetters} from 'vuex';
68
69
70export default {
71 props: [
72 'entry',
73 'index',
74 'editable',
75 ],
76 computed: {
77 ...mapGetters({
78 getProject: 'projects/getProject',
79 getClient: 'clients/getClient',
80 getTask: 'tasks/getTask',
81 }),
82 },
83 methods: {
84 ...mapActions({
85 deleteEntry: 'entries/deleteEntry',
86 }),
87 formatDateTime(datetime) {
88 if (datetime) return this.$moment(datetime).format('h:mm a');
89 else return '-';
90 },
91 },
92};
93</script>
94
95
96<style lang="scss">
97#entry-rows {
98 .row:not(.inset-row) {
99 border-top: 1px solid #eceeef;
100
101 &:nth-of-type(2n+1) {
102 background-color: #fbf3e5 !important;
103 }
104
105 &:last-child {
106 border-bottom: 1px solid #eceeef;
107 }
108
109 // Fix for override of striping-row-color above
110 &.bg-success {
111 background-color: #28a745 !important;
112 }
113 }
114
115 .duration {
116 display: flex;
117 font-weight: lighter;
118 }
119
120 .datetimes {
121 display: none;
122 }
123
124 .entry-note {
125 overflow: hidden;
126 white-space: nowrap;
127 text-overflow: ellipsis;
128 }
129
130 .entry:hover,
131 .entry:focus {
132 .duration {
133 display: none;
134 }
135
136 .datetimes {
137 display: flex;
138 }
139
140 .tasks .small {
141 display: none;
142 }
143
144 .tasks {
145 align-self: flex-start !important;
146
147 .entry-note {
148 overflow: show !important;
149 white-space: normal !important;
150 }
151 }
152 }
153
154 .duration.display-4 {
155 font-size: 1.7rem;
156 }
157
158 .note {
159 line-height: 1.3;
160 margin-bottom: 3px;
161 }
162
163 .username {
164 overflow-x: hidden;
165 }
166
167 .client-project {
168 overflow-x: hidden;
169 text-overflow: ellipsis;
170 white-space: nowrap;
171 }
172
173 .inset-row {
174 padding: 1rem 0;
175
176 .display-4 {
177 font-size: 1.5rem;
178 font-weight: bold;
179 margin-bottom: 0;
180 }
181 }
182}
183</style>