Добавил роутинг
И всякие косметические правки
This commit is contained in:
parent
a031c12a48
commit
a002d130e0
@ -1,6 +1,6 @@
|
|||||||
root = true
|
root = true
|
||||||
|
|
||||||
[*.cr]
|
[*.{cr,vue,js}]
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
|
115
assets/App.vue
115
assets/App.vue
@ -1,110 +1,37 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="app">
|
<main id="app">
|
||||||
<section
|
<nav id="nav">
|
||||||
v-if="today_time"
|
<router-link class="nav-link" :to="{ name: 'timer' }">Таймер</router-link>
|
||||||
class="timer"
|
<router-link class="nav-link" :to="{ name: 'statistics' }"
|
||||||
v-bind:class="{ overtime: today_time.isOvertime() }"
|
>Статистика</router-link
|
||||||
>
|
>
|
||||||
{{ today_time.toStr() }}
|
</nav>
|
||||||
</section>
|
<router-view></router-view>
|
||||||
<section class="actions">
|
</main>
|
||||||
<a v-if="started" v-on:click.prevent="finish" href="#">Закончить</a>
|
|
||||||
<a v-else v-on:click.prevent="start" href="#">Начать</a>
|
|
||||||
</section>
|
|
||||||
<section v-if="total_time" class="total">
|
|
||||||
Всего
|
|
||||||
<span v-bind:class="{ overtime: total_time.isOvertime() }">
|
|
||||||
{{ total_time.toStr() }}
|
|
||||||
</span>
|
|
||||||
</section>
|
|
||||||
<p class="profile-info">Профиль: {{ profileId }}</p>
|
|
||||||
<a href="#" v-on:click.prevent="show_stat = !show_stat">Статистика</a>
|
|
||||||
<div v-if="show_stat">
|
|
||||||
<table class="stat-table">
|
|
||||||
<tr v-for="item in statistics">
|
|
||||||
<td>{{ item.date }}</td>
|
|
||||||
<td>{{ item.planned.total_minutes }}</td>
|
|
||||||
<td>{{ item.worked.total_minutes }}</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script></script>
|
||||||
import TimeSpan from './TimeSpan';
|
|
||||||
import h from './helpers';
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
profileId: null,
|
|
||||||
started: false,
|
|
||||||
total_time: null,
|
|
||||||
today_time: null,
|
|
||||||
show_stat: false,
|
|
||||||
statistics: [],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.profileId = h.extract_profile_id();
|
|
||||||
this.get_status();
|
|
||||||
this.get_statistics();
|
|
||||||
setInterval(() => this.get_status(), 60 * 1000);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
get_status() {
|
|
||||||
h.get_status(this.profileId).then(data => {
|
|
||||||
this.started = data.started;
|
|
||||||
this.total_time = TimeSpan.fromObject(data.total.time);
|
|
||||||
this.today_time = TimeSpan.fromObject(data.today.time);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
get_statistics() {
|
|
||||||
h.get_statistics(this.profileId).then(data => {
|
|
||||||
this.statistics = data;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
start() {
|
|
||||||
h.start(this.profileId).then(() => this.get_status());
|
|
||||||
},
|
|
||||||
finish() {
|
|
||||||
h.finish(this.profileId).then(() => this.get_status());
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
#app {
|
#app {
|
||||||
font-family: 'Avenir', Helvetica, Arial, sans-serif;
|
font-family: 'Avenir', Helvetica, Arial, sans-serif;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
max-width: 400px;
|
||||||
|
display: block;
|
||||||
|
margin: 2em auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#nav {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: 3em;
|
margin: 2em auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timer {
|
.nav-link {
|
||||||
font-size: 600%;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.overtime {
|
.nav-link + .nav-link {
|
||||||
color: green;
|
margin-left: 0.6em;
|
||||||
}
|
|
||||||
|
|
||||||
.actions {
|
|
||||||
font-size: 240%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.total {
|
|
||||||
margin-top: 1em;
|
|
||||||
font-size: 200%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile-info {
|
|
||||||
margin-top: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-table {
|
|
||||||
display: inline-table;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
75
assets/StatisticsPage.vue
Normal file
75
assets/StatisticsPage.vue
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<template>
|
||||||
|
<table class="stat-table">
|
||||||
|
<tr>
|
||||||
|
<th>Дата</th>
|
||||||
|
<th>План</th>
|
||||||
|
<th>Работа</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="item in statistics">
|
||||||
|
<td class="date">{{ item.date }}</td>
|
||||||
|
<td class="planned">{{ item.planned.total_minutes | tt }}</td>
|
||||||
|
<td class="worked">{{ item.worked.total_minutes | tt }}</td>
|
||||||
|
<td class="worked">
|
||||||
|
{{ (item.worked.total_minutes - item.planned.total_minutes) | td }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import h from './helpers';
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
statistics: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.profileId = h.extract_profile_id();
|
||||||
|
this.get_statistics();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
get_statistics() {
|
||||||
|
h.get_statistics(this.profileId).then(data => {
|
||||||
|
this.statistics = data;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
filters: {
|
||||||
|
tt(v) {
|
||||||
|
const h = Math.round(v / 60);
|
||||||
|
const m = v % 60;
|
||||||
|
return (h ? h : '00') + ':' + String(m).padStart(2, '0');
|
||||||
|
},
|
||||||
|
td(v) {
|
||||||
|
const o = v > 0;
|
||||||
|
const a = Math.abs(v);
|
||||||
|
const h = Math.round(a / 60);
|
||||||
|
const m = a % 60;
|
||||||
|
const t = (h ? h : '00') + ':' + String(m).padStart(2, '0');
|
||||||
|
return (o ? '+' : '-') + t;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.stat-table {
|
||||||
|
margin: 1em auto;
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 10px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date,
|
||||||
|
.planned,
|
||||||
|
.worked {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
90
assets/TimerPage.vue
Normal file
90
assets/TimerPage.vue
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
<template>
|
||||||
|
<div class="timer-page">
|
||||||
|
<section
|
||||||
|
v-if="today_time"
|
||||||
|
class="timer"
|
||||||
|
v-bind:class="{ overtime: today_time.isOvertime() }"
|
||||||
|
>
|
||||||
|
{{ today_time.toStr() }}
|
||||||
|
</section>
|
||||||
|
<section class="actions">
|
||||||
|
<a v-if="started" v-on:click.prevent="finish" href="#">Закончить</a>
|
||||||
|
<a v-else v-on:click.prevent="start" href="#">Начать</a>
|
||||||
|
</section>
|
||||||
|
<section v-if="total_time" class="total">
|
||||||
|
{{
|
||||||
|
total_time.isOvertime()
|
||||||
|
? 'Переработка на начало дня'
|
||||||
|
: 'Долг на начало дня'
|
||||||
|
}}
|
||||||
|
<span v-bind:class="{ overtime: total_time.isOvertime() }">
|
||||||
|
{{ total_time.toStr() }}
|
||||||
|
</span>
|
||||||
|
</section>
|
||||||
|
<p class="profile-info">Профиль: {{ profileId }}</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import TimeSpan from './TimeSpan';
|
||||||
|
import h from './helpers';
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
profileId: null,
|
||||||
|
started: false,
|
||||||
|
total_time: null,
|
||||||
|
today_time: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.profileId = h.extract_profile_id();
|
||||||
|
this.get_status();
|
||||||
|
setInterval(() => this.get_status(), 60 * 1000);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
get_status() {
|
||||||
|
h.get_status(this.profileId).then(data => {
|
||||||
|
this.started = data.started;
|
||||||
|
this.total_time = TimeSpan.fromObject(data.total.time);
|
||||||
|
this.today_time = TimeSpan.fromObject(data.today.time);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
start() {
|
||||||
|
h.start(this.profileId).then(() => this.get_status());
|
||||||
|
},
|
||||||
|
finish() {
|
||||||
|
h.finish(this.profileId).then(() => this.get_status());
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.timer-page {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timer {
|
||||||
|
font-size: 600%;
|
||||||
|
margin: 0.25em auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overtime {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
font-size: 240%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total {
|
||||||
|
margin-top: 3em;
|
||||||
|
margin-bottom: 3em;
|
||||||
|
font-size: 140%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-info {
|
||||||
|
margin-top: 2em;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,10 +1,24 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
import VueRouter from 'vue-router';
|
||||||
import App from './App.vue';
|
import App from './App.vue';
|
||||||
|
|
||||||
Vue.config.productionTip = false;
|
import TimerPage from './TimerPage.vue';
|
||||||
|
import StatisticsPage from './StatisticsPage.vue';
|
||||||
|
|
||||||
|
Vue.use(VueRouter);
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{ path: '/', name: 'timer', component: TimerPage },
|
||||||
|
{ path: '/statistics', name: 'statistics', component: StatisticsPage },
|
||||||
|
];
|
||||||
|
|
||||||
|
const router = new VueRouter({
|
||||||
|
routes,
|
||||||
|
});
|
||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
// router,
|
el: '#app',
|
||||||
|
router,
|
||||||
// store,
|
// store,
|
||||||
render: h => h(App),
|
render: h => h(App),
|
||||||
}).$mount('#app');
|
});
|
||||||
|
8
package-lock.json
generated
8
package-lock.json
generated
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "homepage",
|
"name": "dayoff",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
@ -6783,6 +6783,12 @@
|
|||||||
"vue-style-loader": "^4.1.0"
|
"vue-style-loader": "^4.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"vue-router": {
|
||||||
|
"version": "3.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.1.6.tgz",
|
||||||
|
"integrity": "sha512-GYhn2ynaZlysZMkFE5oCHRUTqE8BWs/a9YbKpNLi0i7xD6KG1EzDqpHQmv1F5gXjr8kL5iIVS8EOtRaVUEXTqA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"vue-style-loader": {
|
"vue-style-loader": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.2.tgz",
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
"underscore": "^1.9.2",
|
"underscore": "^1.9.2",
|
||||||
"vue": "^2.6.11",
|
"vue": "^2.6.11",
|
||||||
"vue-loader": "^15.9.0",
|
"vue-loader": "^15.9.0",
|
||||||
|
"vue-router": "^3.1.6",
|
||||||
"vue-style-loader": "^4.1.2",
|
"vue-style-loader": "^4.1.2",
|
||||||
"vue-template-compiler": "^2.6.11",
|
"vue-template-compiler": "^2.6.11",
|
||||||
"webpack": "^4.41.6",
|
"webpack": "^4.41.6",
|
||||||
|
@ -40,7 +40,7 @@ end
|
|||||||
|
|
||||||
get "/api/status" do |env|
|
get "/api/status" do |env|
|
||||||
profile = app.profile Dayoff::ProfileId.new(env.get("profile_id").to_s)
|
profile = app.profile Dayoff::ProfileId.new(env.get("profile_id").to_s)
|
||||||
total_span = profile.total_status now
|
total_span = profile.total_status now.at_beginning_of_day
|
||||||
today_span = profile.date_status now
|
today_span = profile.date_status now
|
||||||
data = {
|
data = {
|
||||||
started: profile.started?,
|
started: profile.started?,
|
||||||
|
Loading…
Reference in New Issue
Block a user