repos
/ timestrap-django master

timestrap-django

mirror archived upstream

Time tracking you can host anywhere, built on Django. Full export support in multiple formats and easily extensible.

djangodockerhandcodedpythonself-hostedtime-trackingtimetracker

6.3 KB · 247 lines · vue Raw History
  1<template>
  2  <div class="bg-light py-2">
  3    <form id="tracker" class="container-fluid" @submit.prevent="submitEntry">
  4      <div class="row">
  5        <div class="col-sm-3 px-2" @keyup.enter.prevent>
  6          <selector
  7            id="tracker-client"
  8            v-model="client"
  9            :options="clients"
 10            :selected="client"
 11            placeholder="Client"
 12          />
 13        </div>
 14
 15        <div class="col-sm-3 px-2" @keyup.enter.prevent>
 16          <selector
 17            id="tracker-project"
 18            v-model="project"
 19            :options="projects"
 20            :selected="project"
 21            placeholder="Project"
 22            required
 23          />
 24        </div>
 25
 26        <div class="col-sm-3 px-2" @keyup.enter.prevent>
 27          <selector
 28            id="tracker-task"
 29            v-model="task"
 30            :options="tasks"
 31            :selected="task"
 32            placeholder="Task"
 33            required
 34          />
 35        </div>
 36
 37        <div class="col-sm-3 px-2">
 38          <datepicker
 39            v-model="date"
 40            id="tracker-date"
 41            type="text"
 42            class="form-control date-input text-center"
 43            placeholder="Date"
 44          />
 45        </div>
 46      </div>
 47
 48      <div class="row mt-2">
 49        <div class="col-sm-6 px-2">
 50          <input
 51            id="tracker-note"
 52            v-model="note"
 53            class="form-control w-100 shadow-sm"
 54            placeholder="Note"
 55            type="text"
 56          />
 57        </div>
 58
 59        <div class="col-sm-3 px-2">
 60          <input
 61            id="tracker-duration"
 62            v-model="duration"
 63            class="form-control text-right font-weight-bold w-100 shadow-sm"
 64            placeholder="0:00"
 65            type="text"
 66            required
 67          />
 68        </div>
 69
 70        <div class="col-sm-3 px-2">
 71          <button
 72            v-if="!running && !duration"
 73            class="btn btn-success w-100"
 74            @click.prevent.exact="toggle"
 75          >
 76            <icon :icon="['fas', 'play']" class="mr-1" />
 77            Start
 78          </button>
 79          <button
 80            v-else-if="running"
 81            class="btn btn-danger w-100"
 82            @click.prevent.exact="toggle"
 83          >
 84            <icon :icon="['fas', 'stop']" class="mr-1" />
 85            Stop ({{ seconds }})
 86          </button>
 87          <button
 88            v-else-if="!running && duration"
 89            :disabled="!canSubmit"
 90            id="tracker-submit"
 91            class="btn btn-info w-100"
 92            type="submit"
 93          >
 94            <icon :icon="['fas', 'plus']" class="mr-1" />
 95            Add
 96          </button>
 97        </div>
 98      </div>
 99    </form>
100  </div>
101</template>
102
103<script>
104import { mapGetters, mapActions } from "vuex";
105
106import Selector from "./selector.vue";
107import Datepicker from "./datepicker.vue";
108
109function getFromLocalStorage(key, defaultValue) {
110  if (!key in window.localStorage) {
111    return defaultValue;
112  }
113  if (defaultValue === null && window.localStorage[key] === "null") {
114    return null;
115  }
116  if (defaultValue === false) {
117    return window.localStorage[key] === "true";
118  }
119  return window.localStorage[key];
120}
121
122export default {
123  components: {
124    Selector,
125    Datepicker
126  },
127  data() {
128    return {
129      task: getFromLocalStorage("tracker_task", null),
130      client: null,
131      project: getFromLocalStorage("tracker_project", null),
132      note: getFromLocalStorage("tracker_note", null),
133      duration: null,
134      datetimeStart:
135        "tracker_start" in window.localStorage
136          ? new Date(Number(window.localStorage.tracker_start))
137          : null,
138      datetimeEnd: null,
139      date: this.$moment().format("YYYY-MM-DD"),
140
141      running: getFromLocalStorage("tracker_running", false),
142      total: 0,
143      seconds: "0"
144    };
145  },
146  computed: {
147    ...mapGetters({
148      clients: "clients/getSelectClients",
149      tasks: "tasks/getSelectTasks",
150      projectsAll: "projects/getSelectProjects",
151      projectsClient: "projects/getSelectProjectsForClient",
152      users: "users/getSelectUsers",
153      getProject: "projects/getProject"
154    }),
155    projects() {
156      if (!this.client) {
157        return this.projectsAll;
158      } else {
159        return this.projectsClient(this.client);
160      }
161    },
162    canSubmit() {
163      return this.project && this.task;
164    }
165  },
166  watch: {
167    client() {
168      if (this.project) {
169        const p = this.getProject();
170        if (p.client !== this.client) {
171          this.project = null;
172        }
173      }
174    },
175    project() {
176      window.localStorage.tracker_project = this.project;
177    },
178    task() {
179      window.localStorage.tracker_task = this.task;
180    },
181    note() {
182      window.localStorage.tracker_note = this.note;
183    }
184  },
185  created() {
186    if (this.running) {
187      this.tick();
188      this.start();
189    }
190  },
191  methods: {
192    ...mapActions({
193      createEntry: "entries/createEntry"
194    }),
195    submitEntry() {
196      this.createEntry({
197        task: this.task,
198        project: this.project,
199        note: this.note,
200        duration: this.duration,
201        datetime_start: this.datetimeStart,
202        datetime_end: this.datetimeEnd,
203        user: timestrapConfig.USER.URL,
204        date: this.date //this.$moment().format('YYYY-MM-DD'),
205      });
206      this.reset();
207      this.note = null;
208    },
209    reset() {
210      this.duration = null;
211      this.total = 0;
212      this.seconds = "0";
213    },
214    tick() {
215      this.total = Math.floor(
216        (Date.now() - this.datetimeStart.valueOf()) / 1000
217      );
218      this.seconds = (this.total % 3600) % 60;
219      this.duration = this.$moment
220        .duration(this.total, "seconds")
221        .format("h:mm", { trim: false });
222      document.title = this.duration + " — " + timestrapConfig.SITE.NAME;
223    },
224    start() {
225      this.interval = setInterval(this.tick, 1000, this);
226    },
227    toggle() {
228      this.running = !this.running;
229      window.localStorage.tracker_running = this.running;
230
231      if (this.running) {
232        this.datetimeStart = new Date(Date.now());
233        window.localStorage.tracker_start = this.datetimeStart.getTime();
234        this.reset();
235        this.start();
236      } else {
237        this.datetimeEnd = new Date(Date.now());
238        window.localStorage.tracker_start = null;
239        clearInterval(this.interval);
240        if (this.total < 60) this.reset();
241        document.title = "Application — " + timestrapConfig.SITE.NAME;
242      }
243    }
244  }
245};
246</script>