Basic site working
This commit is contained in:
parent
1be0761358
commit
045eb7ecc9
16 changed files with 494 additions and 256 deletions
|
@ -12,7 +12,7 @@
|
|||
"bootstrap": "^4.5.3",
|
||||
"bootstrap-vue": "^2.21.1",
|
||||
"core-js": "^3.6.5",
|
||||
"vue": "^2.6.11",
|
||||
"vue": "^2.6.12",
|
||||
"vue-router": "^3.2.0",
|
||||
"vuex": "^3.4.0"
|
||||
},
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<b-img
|
||||
:src="require('../../assets/logo.png')"
|
||||
alt="Juggl"
|
||||
:height="heightSize"
|
||||
:width="widthSize"
|
||||
:center="center"
|
||||
/>
|
||||
</b-link>
|
||||
|
@ -25,13 +25,13 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
heightSize: function() {
|
||||
widthSize: function() {
|
||||
let sizes = {
|
||||
mini: "35px",
|
||||
normal: "64px",
|
||||
tiny: "80px",
|
||||
smaller: "110px",
|
||||
small: "150px",
|
||||
normal: "175px",
|
||||
medium: "300px",
|
||||
large: "450px",
|
||||
big: "600px",
|
||||
|
|
22
juggl-vue/src/components/base/BaseUserDropdown.vue
Normal file
22
juggl-vue/src/components/base/BaseUserDropdown.vue
Normal file
|
@ -0,0 +1,22 @@
|
|||
<template>
|
||||
<b-dropdown id="dropdown" :text=this.username variant="outline-danger" right>
|
||||
<b-dropdown-item to="/logout">Log out</b-dropdown-item>
|
||||
</b-dropdown>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import store from "@/store";
|
||||
|
||||
export default {
|
||||
name: "BaseUserDropdown",
|
||||
computed: {
|
||||
username: () => store.getters.user.name,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
#dropdown
|
||||
*
|
||||
line-height: 1.5
|
||||
</style>
|
|
@ -1,10 +1,9 @@
|
|||
<template>
|
||||
<b-form @submit="submitForm">
|
||||
<b-form-group id="email-group" label-for="email">
|
||||
<b-form-group id="username-group" label-for="username">
|
||||
<b-form-input
|
||||
id="email"
|
||||
id="username"
|
||||
v-model="form.user_id"
|
||||
type="number"
|
||||
required
|
||||
placeholder="User ID"
|
||||
trim
|
||||
|
@ -24,7 +23,8 @@
|
|||
<b-form-invalid-feedback :state="!failed">
|
||||
Password or email invalid.
|
||||
</b-form-invalid-feedback>
|
||||
<b-button variant="primary" type="submit" block>
|
||||
<b-button variant="primary" type="submit" block :disabled="working">
|
||||
<b-spinner v-if="working" small/>
|
||||
Log in
|
||||
</b-button>
|
||||
</b-form>
|
||||
|
@ -41,7 +41,8 @@ export default {
|
|||
user_id: null,
|
||||
api_key: null
|
||||
},
|
||||
failed: false
|
||||
failed: false,
|
||||
working: false
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
@ -50,14 +51,16 @@ export default {
|
|||
*/
|
||||
submitForm: function(e) {
|
||||
e.preventDefault();
|
||||
this.failed = false;
|
||||
this.working = true;
|
||||
|
||||
// Try to login
|
||||
store.dispatch("login")
|
||||
.login(this.form.user_id, this.form.api_key)
|
||||
store.dispatch("login", { userId: this.form.user_id, apiKey: this.form.api_key})
|
||||
.then(r => {
|
||||
if (r !== true) {
|
||||
this.failed = true;
|
||||
return;
|
||||
this.working = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
// On success redirect to target or dashboard
|
||||
|
@ -68,6 +71,7 @@ export default {
|
|||
.catch(e => {
|
||||
console.log(e);
|
||||
this.failed = true;
|
||||
this.working = false;
|
||||
});
|
||||
|
||||
return false;
|
||||
|
|
74
juggl-vue/src/components/juggl/JugglProjectsPanel.vue
Normal file
74
juggl-vue/src/components/juggl/JugglProjectsPanel.vue
Normal file
|
@ -0,0 +1,74 @@
|
|||
<template>
|
||||
<div id="project-list">
|
||||
<div v-for="project in projects" :key="project.project_id" @click="() => startProject(project.project_id)">
|
||||
<h1>{{project.name}}</h1>
|
||||
<p>{{getDurationTimestamp(project)}}</p>
|
||||
<p>{{project.record_count}} records</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import store from "@/store"
|
||||
|
||||
export default {
|
||||
name: "JugglProjectPanel",
|
||||
props: {
|
||||
projects: {
|
||||
required: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getDurationTimestamp: (project) => {
|
||||
var totalSeconds = project.duration;
|
||||
// var days = Math.floor(totalSeconds / 86400);
|
||||
var hours = Math.floor(totalSeconds / 3600); //Math.floor((totalSeconds - (days * 86400)) / 3600);
|
||||
var minutes = Math.floor((totalSeconds - (hours * 3600)) / 60);
|
||||
var seconds = totalSeconds - (hours * 3600) - (minutes * 60);
|
||||
|
||||
// if (days < 10) {days = "0"+days;}
|
||||
if (hours < 10) {hours = "0"+hours;}
|
||||
if (minutes < 10) {minutes = "0"+minutes;}
|
||||
if (seconds < 10) {seconds = "0"+seconds;}
|
||||
return hours+':'+minutes+':'+seconds;
|
||||
},
|
||||
startProject: function (id) {
|
||||
store.dispatch("startRecord", id);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
@import '@/style/theme.sass'
|
||||
|
||||
#project-list
|
||||
display: flex
|
||||
flex-direction: row
|
||||
flex-wrap: wrap
|
||||
justify-content: center
|
||||
align-content: flex-start
|
||||
padding: 5px
|
||||
|
||||
> *
|
||||
margin: 5px
|
||||
border: 1px dashed $grey
|
||||
border-radius: 5px
|
||||
padding: 10px
|
||||
|
||||
&:hover
|
||||
border-color: $primary !important
|
||||
cursor: pointer
|
||||
|
||||
h1
|
||||
font-weight: bold
|
||||
font-size: 24pt
|
||||
margin: 0px
|
||||
padding: 0px
|
||||
|
||||
p
|
||||
font-size: 10pt
|
||||
color: $grey
|
||||
margin: 0px
|
||||
padding: 0px
|
||||
</style>
|
97
juggl-vue/src/components/juggl/JugglRecordsList.vue
Normal file
97
juggl-vue/src/components/juggl/JugglRecordsList.vue
Normal file
|
@ -0,0 +1,97 @@
|
|||
<template>
|
||||
<b-table :items="records" hover :busy="isLoading" :fields="fields">
|
||||
<template #table-busy>
|
||||
<div class="text-center">
|
||||
<b-spinner></b-spinner>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Custom data -->
|
||||
<template #cell(project)="data">
|
||||
{{ getProject(data.item.project_id).name }}
|
||||
</template>
|
||||
|
||||
<template #cell(start)="data">
|
||||
{{ data.item.start_time }}
|
||||
</template>
|
||||
|
||||
<template #cell(duration)="data">
|
||||
{{ data.item.duration }}
|
||||
</template>
|
||||
|
||||
<template #cell(stop)="data">
|
||||
<b-button size="sm" @click="stopRecord(data.item.record_id)" variant="outline-success">
|
||||
<b-icon class="icon-btn" icon="check" />
|
||||
</b-button>
|
||||
</template>
|
||||
|
||||
<template #cell(abort)="data">
|
||||
<b-button size="sm" @click="() => abortRecord(data.item.record_id)" variant="outline-danger">
|
||||
<b-icon class="icon-btn" icon="x" />
|
||||
</b-button>
|
||||
</template>
|
||||
</b-table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import store from "@/store";
|
||||
|
||||
export default {
|
||||
name: "JugglRecordsList",
|
||||
props: {
|
||||
records: {
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
iconScale: 1.6,
|
||||
fields: [
|
||||
{
|
||||
key: "project",
|
||||
label: "Project",
|
||||
},
|
||||
{
|
||||
key: "start",
|
||||
label: "Start",
|
||||
},
|
||||
{
|
||||
key: "duration",
|
||||
label: "Duration",
|
||||
},
|
||||
{
|
||||
key: "stop",
|
||||
label: "Stop",
|
||||
},
|
||||
{
|
||||
key: "abort",
|
||||
label: "Abort",
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isLoading: function () {
|
||||
return this.records === undefined;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getProject: function (id) {
|
||||
return store.getters.getProjectById(id);
|
||||
},
|
||||
stopRecord: function (id) {
|
||||
store.dispatch("endRecord", id);
|
||||
},
|
||||
abortRecord: function (id) {
|
||||
console.log(id);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
.icon-btn:hover
|
||||
color: green
|
||||
cursor: pointer
|
||||
|
||||
</styl>
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div>
|
||||
<BaseLogo id="logo" size="smaller" center class="space-top" />
|
||||
<BaseLogo id="logo" size="medium" center class="space-top" />
|
||||
<BaseContainer :width="width" center class="space-bottom">
|
||||
<BaseTitle v-if="title" center size="huge" class="centered">
|
||||
{{ title }}
|
||||
|
|
|
@ -4,23 +4,11 @@
|
|||
<BaseContainer width="wide" center>
|
||||
<ul id="header-container">
|
||||
<li>
|
||||
<BaseLogo size="normal"/>
|
||||
<BaseLogo id="logo" size="normal"/>
|
||||
</li>
|
||||
<li class="right">
|
||||
<BaseUserDropdown/>
|
||||
</li>
|
||||
<!-- <li style="float: right">
|
||||
<input
|
||||
id="user-id"
|
||||
class="public hidden"
|
||||
type="text"
|
||||
placeholder="User ID"
|
||||
/>
|
||||
<input
|
||||
id="api-key"
|
||||
class="public hidden"
|
||||
type="password"
|
||||
placeholder="API Key"
|
||||
/>
|
||||
<button id="auth-btn" onclick="handleAuthBtn()">Log In</button>
|
||||
</li> -->
|
||||
</ul>
|
||||
</BaseContainer>
|
||||
</header>
|
||||
|
@ -35,12 +23,14 @@
|
|||
<script>
|
||||
import BaseContainer from "@/components/base/BaseContainer.vue";
|
||||
import BaseLogo from "@/components/base/BaseLogo.vue";
|
||||
import BaseUserDropdown from "@/components/base/BaseUserDropdown.vue";
|
||||
|
||||
export default {
|
||||
name: "LayoutNavbarPrivate",
|
||||
components: {
|
||||
BaseContainer,
|
||||
BaseLogo
|
||||
BaseLogo,
|
||||
BaseUserDropdown
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
@ -62,12 +52,11 @@ header
|
|||
list-style: none
|
||||
line-height: navbar-height
|
||||
|
||||
img
|
||||
margin-top: 0.5rem
|
||||
height: 3rem
|
||||
width: auto
|
||||
.right
|
||||
float: right
|
||||
|
||||
*
|
||||
line-height: $navbar-height
|
||||
vertical-align: baseline
|
||||
display: inline-block
|
||||
|
||||
|
|
|
@ -1,21 +1,34 @@
|
|||
import Vue from "vue";
|
||||
import VueRouter from "vue-router";
|
||||
import store from "../store";
|
||||
import Login from "../views/Login.vue";
|
||||
import Home from "../views/Home.vue";
|
||||
|
||||
Vue.use(VueRouter);
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: "/",
|
||||
redirect: "/home"
|
||||
},
|
||||
{
|
||||
path: "/login",
|
||||
name: "Login",
|
||||
component: Login,
|
||||
},
|
||||
{
|
||||
path: "/",
|
||||
path: "/home",
|
||||
name: "Home",
|
||||
component: Home,
|
||||
// beforeEnter: requireAuth,
|
||||
beforeEnter: requireAuth,
|
||||
},
|
||||
{
|
||||
path: "/logout",
|
||||
name: "Logout",
|
||||
beforeEnter: (to, from, next) => {
|
||||
store.dispatch("logout");
|
||||
next("/");
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -28,16 +41,15 @@ const router = new VueRouter({
|
|||
/**
|
||||
* Checks authentication before proceeding
|
||||
*/
|
||||
// TODO: Authentication required in router
|
||||
// function requireAuth(to, from, next) {
|
||||
// if (!userService.isLoggedIn()) {
|
||||
// next({
|
||||
// path: "/login",
|
||||
// query: { redirect: to.fullPath },
|
||||
// });
|
||||
// } else {
|
||||
// next();
|
||||
// }
|
||||
// }
|
||||
function requireAuth(to, from, next) {
|
||||
if (!store.getters.isLoggedIn) {
|
||||
next({
|
||||
path: "/login",
|
||||
query: { redirect: to.fullPath },
|
||||
});
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
||||
export default router;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import axios from "axios";
|
||||
import { store } from "@/store";
|
||||
import store from "@/store";
|
||||
|
||||
/**
|
||||
* A wrapper for the used fetch API, currently axios.
|
||||
|
@ -15,11 +15,19 @@ export const apiService = {
|
|||
},
|
||||
|
||||
post(resource, json, options) {
|
||||
return this.getApi().post(resource, json, options);
|
||||
return this.getApi().post(
|
||||
resource,
|
||||
{ ...this.getDefaultJson(), ...json },
|
||||
options
|
||||
);
|
||||
},
|
||||
|
||||
put(resource, json, options) {
|
||||
return this.getApi().put(resource, json, options);
|
||||
return this.getApi().put(
|
||||
resource,
|
||||
{ ...this.getDefaultJson(), ...json },
|
||||
options
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -32,4 +40,10 @@ export const apiService = {
|
|||
|
||||
return axios.create(options);
|
||||
},
|
||||
getDefaultJson() {
|
||||
return {
|
||||
user_id: store.getters.auth.userId,
|
||||
api_key: store.getters.auth.apiKey,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// TODO: Do I need this file?
|
||||
|
||||
/**
|
||||
* Contains a bunch of helper functions.
|
||||
*/
|
||||
|
|
|
@ -11,212 +11,88 @@ export const jugglService = {
|
|||
* @returns A promise
|
||||
*/
|
||||
getUser() {
|
||||
return apiService.post("/getUser.php");
|
||||
return apiService.post("/getUser.php").then((r) => {
|
||||
return {
|
||||
data: r.data,
|
||||
msg: "",
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
getProjects() {
|
||||
return apiService.post("/getProjects.php");
|
||||
return apiService.post("/getProjects.php").then((r) => {
|
||||
return {
|
||||
data: r.data,
|
||||
msg: "",
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
getRecord(recordId) {
|
||||
return apiService
|
||||
.post("/getRecord.php", {
|
||||
record_id: recordId,
|
||||
})
|
||||
.then((r) => {
|
||||
return {
|
||||
data: processRecords(r.data),
|
||||
msg: "",
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
getRecords() {
|
||||
return apiService.post("/getRecords.php");
|
||||
return apiService.post("/getRecords.php").then((r) => {
|
||||
return {
|
||||
data: processRecords(r.data),
|
||||
msg: "",
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
getRunningRecords() {
|
||||
return apiService.post("/getRunningRecords.php");
|
||||
return apiService.post("/getRunningRecords.php").then((r) => {
|
||||
return {
|
||||
data: processRecords(r.data),
|
||||
msg: "",
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
startRecord(projectId, startTime = null) {
|
||||
if (startTime == null) startTime = new Date();
|
||||
return apiService.post("/startRecord.php", {
|
||||
project_id: projectId,
|
||||
start_time: helperService.dateToString(startTime),
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Requests a list of all semesters from the current user.
|
||||
*
|
||||
* @returns A promise
|
||||
*/
|
||||
getSemesterList() {
|
||||
return apiService.get("/semester");
|
||||
},
|
||||
|
||||
/**
|
||||
* Requests all the data from a specific semester from the current user.
|
||||
*
|
||||
* @param {*} semesterNr Nr starting at 1 from the desired semester to gather data from
|
||||
*
|
||||
* @returns A promise
|
||||
*/
|
||||
getSemester(semesterNr) {
|
||||
var path = "/semester/" + semesterNr;
|
||||
return apiService.get(path).then((semester) => {
|
||||
// Add semester nr to modules
|
||||
semester.data.modules.forEach((module) => {
|
||||
module.semester_nr = semester.nr;
|
||||
});
|
||||
|
||||
return semester;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a new semester to the current user.
|
||||
*
|
||||
* @param {*} data An object with {nr, startdate, enddate}
|
||||
*
|
||||
* @returns A promise
|
||||
*/
|
||||
addSemester(data) {
|
||||
var path = "/semester";
|
||||
return apiService.put(path, data);
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a new module to the current user.
|
||||
*
|
||||
* @param {*} semesterNr Associated semester nr
|
||||
* @param {*} data An object with {name, required_total, required_per_sheet}
|
||||
*
|
||||
* @returns A promise
|
||||
*/
|
||||
addModule(semesterNr, data) {
|
||||
var path = "/semester/" + semesterNr + "/module";
|
||||
return apiService.put(path, data);
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a new sheet to the current user.
|
||||
*
|
||||
* @param {*} semesterNr Associated semester nr
|
||||
* @param {*} semesterNr Associated module name
|
||||
* @param {*} data An object with {date, points, total, is_draft, percentage}
|
||||
*
|
||||
* @returns A promise
|
||||
*/
|
||||
addSheet(semesterNr, moduleName, data) {
|
||||
var path =
|
||||
"/semester/" + semesterNr + "/module/" + moduleName + "/sheet";
|
||||
return apiService.put(path, data);
|
||||
},
|
||||
|
||||
/**
|
||||
* Registers a new user.
|
||||
*
|
||||
* @param {*} data An object with {date, points, total, is_draft, percentage}
|
||||
*
|
||||
* @returns A promise
|
||||
*/
|
||||
signUp(data) {
|
||||
var path = "/user/signup";
|
||||
return apiService
|
||||
.post(path, data)
|
||||
.then((r) => {
|
||||
return { success: true, msg: r.data.msg };
|
||||
// Later
|
||||
// if (r.status === 204) return { success: true, msg: "" };
|
||||
// else return { success: false, msg: r.data.msg };
|
||||
.post("/startRecord.php", {
|
||||
project_id: projectId,
|
||||
start_time: helperService.dateToString(startTime),
|
||||
})
|
||||
.catch((r) => {
|
||||
return { success: false, msg: r.response.data.msg };
|
||||
.then((r) => {
|
||||
return {
|
||||
data: r.data,
|
||||
msg: "",
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Verifies a user with a token.
|
||||
*
|
||||
* @param {*} token Server generated token
|
||||
*
|
||||
* @returns A promise
|
||||
*/
|
||||
verify(token) {
|
||||
var path = "/user/verify";
|
||||
endRecord(recordId, endTime = null) {
|
||||
if (endTime == null) endTime = new Date();
|
||||
return apiService
|
||||
.post(path, { token: token })
|
||||
.then((r) => {
|
||||
return { success: true, msg: r.data.msg };
|
||||
.post("/endRecord.php", {
|
||||
record_id: recordId,
|
||||
end_time: helperService.dateToString(endTime),
|
||||
})
|
||||
.catch((r) => {
|
||||
return { success: false, msg: r.response.data.msg };
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Requests all the data from a specific semester from the current user.
|
||||
*
|
||||
* @param {*} semesterNr Nr starting at 1 from the desired semester to gather data from
|
||||
*
|
||||
* @returns A promise
|
||||
*/
|
||||
getModule(semesterNr, moduleName) {
|
||||
var path = "/semester/" + semesterNr + "/module/" + moduleName;
|
||||
|
||||
return apiService.get(path).then((module) => {
|
||||
module.data.semester_nr = semesterNr;
|
||||
|
||||
// Add semester nr and module name to sheets
|
||||
module.data.sheets.forEach((sheet) => {
|
||||
sheet.semester_nr = semesterNr;
|
||||
sheet.module_name = moduleName;
|
||||
});
|
||||
|
||||
return module;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Sends credentials to the server in case of successful authentication, saves the jwt in localStorage.
|
||||
*
|
||||
* @param {*} email Email of user credentials
|
||||
* @param {*} pass Password of user credentials
|
||||
* @returns A promise with on parameter, which is true in case of successful authentication
|
||||
*/
|
||||
login(email, pass) {
|
||||
// Is already logged in?
|
||||
if (this.isLoggedIn()) {
|
||||
this.logout();
|
||||
}
|
||||
|
||||
// Try to log in
|
||||
var user = {
|
||||
email: email,
|
||||
password: pass,
|
||||
};
|
||||
|
||||
return apiService
|
||||
.post("/user/login", user)
|
||||
.then((r) => {
|
||||
localStorage.token = r.data;
|
||||
return true;
|
||||
})
|
||||
.catch(() => {
|
||||
return false;
|
||||
return {
|
||||
data: r.data,
|
||||
msg: "",
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the jwt.
|
||||
*/
|
||||
getToken() {
|
||||
return localStorage.token;
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes the stored jwt and therefor prevents access to any page that requires authentication.
|
||||
*/
|
||||
logout() {
|
||||
delete localStorage.token;
|
||||
apiService.get("/user/logout");
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the current authentication state.
|
||||
*/
|
||||
isLoggedIn() {
|
||||
// This !! converts expression to boolean with unchanged value
|
||||
// First ! converts to boolean and inverts value and second ! inverts again
|
||||
return !!localStorage.token;
|
||||
},
|
||||
};
|
||||
|
||||
function processRecords(data) {
|
||||
Object.values(data.records).forEach(rec => {
|
||||
rec.running = rec.end_time === null;
|
||||
});
|
||||
return data;
|
||||
}
|
|
@ -7,10 +7,10 @@ Vue.use(Vuex);
|
|||
export default new Vuex.Store({
|
||||
state: {
|
||||
apiUrl: "https://juggl.giller.dev/api",
|
||||
apiKey: undefined,
|
||||
projects: {},
|
||||
records: {},
|
||||
user: undefined,
|
||||
projects: [],
|
||||
records: [],
|
||||
auth: undefined,
|
||||
},
|
||||
mutations: {
|
||||
setKey(state, key) {
|
||||
|
@ -22,35 +22,145 @@ export default new Vuex.Store({
|
|||
setRecords(state, records) {
|
||||
state.records = records;
|
||||
},
|
||||
setUser(state, user) {
|
||||
state.user = user;
|
||||
},
|
||||
logout(state) {
|
||||
state.auth = undefined;
|
||||
// TODO: Doesn't work apparently
|
||||
localStorage.removeItem("apiKey");
|
||||
localStorage.removeItem("userId");
|
||||
},
|
||||
login(state, { apiKey, userId }) {
|
||||
state.auth = { apiKey: apiKey, userId: userId };
|
||||
localStorage.setItem("apiKey", apiKey);
|
||||
localStorage.setItem("userId", userId);
|
||||
},
|
||||
},
|
||||
getters: {
|
||||
runningRecords: (state) => {
|
||||
return state.records.filter((record) => record.running);
|
||||
return Object.values(state.records).filter((record) => record.running);
|
||||
},
|
||||
finishedRecords: (state) => {
|
||||
return state.records.filter((record) => !record.running);
|
||||
return Object.values(state.records).filter(
|
||||
(record) => !record.running
|
||||
);
|
||||
},
|
||||
apiKey: (state) => state.apiKey,
|
||||
user: (state) => state.user,
|
||||
auth: (state) => state.auth,
|
||||
apiUrl: (state) => state.apiUrl,
|
||||
isLoggedIn: (state) => !!state.apiKey,
|
||||
user: (state) => state.user,
|
||||
isLoggedIn: (state) => !!state.auth,
|
||||
records: (state) => state.records,
|
||||
projects: (state) => state.projects,
|
||||
projectIds: (state) => {
|
||||
var projectIds = [];
|
||||
Object.values(state.projects).forEach((project) => {
|
||||
projectIds.push(project.project_id);
|
||||
});
|
||||
return projectIds;
|
||||
},
|
||||
runningProjectIds: (state, getters) => {
|
||||
var runningProjectIds = [];
|
||||
Object.values(getters.runningRecords).forEach((record) => {
|
||||
var projectId = record.project_id;
|
||||
if (runningProjectIds.includes(runningProjectIds) === false) {
|
||||
runningProjectIds.push(projectId);
|
||||
}
|
||||
});
|
||||
return runningProjectIds;
|
||||
},
|
||||
finishedProjectIds: (state, getters) => {
|
||||
var runningProjectIds = getters.runningProjectIds;
|
||||
return getters.projectIds.filter(
|
||||
(id) => !runningProjectIds.includes(id)
|
||||
);
|
||||
},
|
||||
finishedProjects: (state, getters) => {
|
||||
var ids = getters.finishedProjectIds;
|
||||
return Object.values(state.projects).filter((project) =>
|
||||
ids.includes(project.project_id)
|
||||
);
|
||||
},
|
||||
runningProjects: (state, getters) => {
|
||||
var ids = getters.runningProjectIds;
|
||||
return Object.values(state.projects).filter((project) =>
|
||||
ids.includes(project.project_id)
|
||||
);
|
||||
},
|
||||
getProjectById: (state, getters) => (id) => {
|
||||
return getters.projects.find(
|
||||
return Object.values(getters.projects).find(
|
||||
(project) => project.project_id === id
|
||||
);
|
||||
},
|
||||
getRecordById: (state, getters) => (id) => {
|
||||
return getters.records.find((record) => record.record_id === id);
|
||||
return Object.values(getters.records).find((record) => record.record_id === id);
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
loadProjects({ commit }) {
|
||||
commit("setProjects");
|
||||
jugglService.getProjects().then((r) => {
|
||||
commit("setProjects", r.data.projects);
|
||||
});
|
||||
},
|
||||
login({ commit, userId, apiKey }) {
|
||||
return jugglService.login();
|
||||
loadAllRecords({ commit }) {
|
||||
jugglService.getRecords().then((r) => {
|
||||
commit("setRecords", r.data.records);
|
||||
});
|
||||
},
|
||||
loadRunningRecords({ commit, getters }) {
|
||||
jugglService.getRunningRecords().then((r) => {
|
||||
var allRecords = {
|
||||
...getters.finishedRecords,
|
||||
...r.data.records,
|
||||
};
|
||||
commit("setRecords", allRecords);
|
||||
});
|
||||
},
|
||||
login({ commit, getters }, { userId, apiKey }) {
|
||||
// Is already logged in?
|
||||
if (getters.isLoggedIn) {
|
||||
this.dispatch("logout");
|
||||
}
|
||||
|
||||
commit("login", { apiKey: apiKey, userId: userId });
|
||||
|
||||
return jugglService
|
||||
.getUser()
|
||||
.catch(() => {
|
||||
this.dispatch("logout");
|
||||
return false;
|
||||
})
|
||||
.then((r) => {
|
||||
commit("setUser", r.data.users[0]);
|
||||
return true;
|
||||
});
|
||||
},
|
||||
logout({ commit }) {
|
||||
commit("setUser", undefined);
|
||||
commit("logout");
|
||||
},
|
||||
endRecord(context, recordId) {
|
||||
return jugglService
|
||||
.endRecord(recordId)
|
||||
.catch(() => {
|
||||
return false;
|
||||
})
|
||||
.then(() => {
|
||||
this.dispatch("loadRunningRecords");
|
||||
this.dispatch("loadProjects");
|
||||
return true;
|
||||
});
|
||||
},
|
||||
startRecord(context, projectId) {
|
||||
return jugglService
|
||||
.startRecord(projectId)
|
||||
.catch(() => {
|
||||
return false;
|
||||
})
|
||||
.then(() => {
|
||||
this.dispatch("loadRunningRecords");
|
||||
return true;
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -6,6 +6,7 @@ $black: #000
|
|||
$white: #fff
|
||||
$primary: #F00
|
||||
$secondary: $grey
|
||||
$red: $primary
|
||||
|
||||
$background-primary: #222
|
||||
$background-secondary: #000
|
||||
|
@ -53,6 +54,23 @@ body
|
|||
a
|
||||
color: $font-link !important
|
||||
|
||||
.b-dropdown, .dropdown
|
||||
border-radius: 0
|
||||
background: #0000
|
||||
|
||||
ul.dropdown-menu
|
||||
background-color: $background-primary
|
||||
border: 1px solid $primary
|
||||
color: $primary
|
||||
|
||||
*:hover
|
||||
color: $white !important
|
||||
background: darken($primary, 20)
|
||||
|
||||
*:active
|
||||
color: $white !important
|
||||
background: $primary
|
||||
|
||||
// .custom-checkbox
|
||||
// label
|
||||
// color: $font-primary !important
|
||||
|
|
|
@ -1,21 +1,46 @@
|
|||
<template>
|
||||
<LayoutNavbarPrivate>
|
||||
Hey there
|
||||
<b-link to="/login">Go to login</b-link>
|
||||
<div v-if="runningRecords.length > 0">
|
||||
<h2 class="center">Tracking</h2>
|
||||
<JugglRecordsList :records="runningRecords"/>
|
||||
</div>
|
||||
<div v-if="finishedProjects.length > 0">
|
||||
<h2 class="center">Projects</h2>
|
||||
<JugglProjectsPanel :projects="finishedProjects"/>
|
||||
</div>
|
||||
</LayoutNavbarPrivate>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LayoutNavbarPrivate from "@/components/layout/LayoutNavbarPrivate";
|
||||
import JugglProjectsPanel from "@/components/juggl/JugglProjectsPanel";
|
||||
import JugglRecordsList from "@/components/juggl/JugglRecordsList";
|
||||
import store from "@/store";
|
||||
|
||||
export default {
|
||||
name: "Home",
|
||||
components: {
|
||||
LayoutNavbarPrivate
|
||||
LayoutNavbarPrivate,
|
||||
JugglProjectsPanel,
|
||||
JugglRecordsList
|
||||
},
|
||||
computed: {
|
||||
finishedProjects: () => {
|
||||
return store.getters.finishedProjects;
|
||||
},
|
||||
runningRecords: () => {
|
||||
return store.getters.runningRecords;
|
||||
}
|
||||
},
|
||||
created: () => {
|
||||
store.dispatch("loadProjects");
|
||||
store.dispatch("loadRunningRecords");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
<style lang="sass">
|
||||
.center
|
||||
text-align: center
|
||||
font-weight: bold
|
||||
</style>
|
|
@ -1,7 +1,6 @@
|
|||
<template>
|
||||
<LayoutMinimal title="Login" width="slimmer">
|
||||
<FormLogin/>
|
||||
<b-link to="/">Go to home</b-link>
|
||||
</LayoutMinimal>
|
||||
</template>
|
||||
|
||||
|
|
Loading…
Reference in a new issue