Browse Source

basic version of the webApp

Davide Alberani 7 years ago
parent
commit
ea03547a10
4 changed files with 351 additions and 73 deletions
  1. 133 56
      src/App.vue
  2. 71 0
      src/Attendee.vue
  3. 124 0
      src/Group.vue
  4. 23 17
      src/main.js

+ 133 - 56
src/App.vue

@@ -1,56 +1,96 @@
 <template>
     <div id="app">
-        <div id="datepicker">
-            <datepicker :value="state.date" :inline="true" v-on:selected="getDay"></datepicker>
-        </div>
-        <div id="panel">
-            <ul class="groups">
-                <li v-for="group in state.day.groups || []">
-                    <div>{{ group.group }}</div>
-                    <ul class="attendees">
-                        <li v-for="attendee in group.attendees || []">
-                            {{attendee.name}}
-                        </li>
-                        <li class="add-attendee">add: <input v-on:keyup.enter="addAttendee(group.group, newAttendee)" v-model="newAttendee" /></li>
-                    </ul>
-                </li>
-                <li class="add-group"><input v-model="newGroup" /></a>
-                <ul v-if="newGroup">
-                    <li class="add-attendee">add: <input v-on:keyup.enter="addAttendee(newGroup, newAttendee)" v-model="newAttendee" /></li>
-                </ul>
-            </ul>
-        </div>
-    <div>
+        <md-toolbar class="md-dense">
+            <h2 id="toolbar-title" class="md-title">ibt2</h2>
+            <span v-if="loggedInUser.username">
+                Logged in: {{ loggedInUser.username }}
+                <md-button class="md-icon-button" @click="logout()">
+                    <md-icon>exit_to_app</md-icon>
+                </md-button>
+            </span>
+            <span v-else>
+                <md-button v-show="!showLoginForm" class="md-icon-button" @click="focusToLoginForm()">
+                    <md-icon>power_settings_new</md-icon>
+                </md-button>
+                <span v-show="showLoginForm">
+                    <md-input-container id="login-form" md-inline>
+                        <md-input ref="usernameInput" @keyup.enter.native="focusToPassword()" v-model="username" placeholder="username" md-inline />
+                        <md-input ref="passwordInput" @keyup.enter.native="login()" v-model="password" placeholder="password" md-line />
+                    </md-input-container>
+                </span>
+            </span>
+        </md-toolbar>
+        <md-layout md-gutter md-row>
+            <md-layout md-column md-flex="20" md-gutter>
+                <datepicker id="datepicker" :value="date" :inline="true" @selected="getDay"></datepicker>
+            </md-layout>
+            <md-layout id="panel" md-column>
+                <md-layout md-row>
+                    <group v-for="group in day.groups || []" :group="group" :day="day.day" new-attendee="" @updated="reload" />
+                    <group :add-new-group="true" :day="day.day" new-attendee="" new-group="" @updated="reload" />
+                </md-layout>
+            </md-layout>
+        </md-layout>
+    </div>
 </template>
-
 <script>
 
 import Datepicker from 'vuejs-datepicker';
+import Group from './Group';
 
 export default {
     data () {
         return {
-            state: {
-                date: new Date(),
-                day: {},
-            },
-            newAttendee: null,
-            newGroup: null
+            date: null, // a Date object representing the selected date
+            day: {},
+            daysSummary: {},
+            username: '',
+            password: '',
+            showLoginForm: false,
+            loggedInUser: {username: ''}
         }
     },
 
     beforeCreate: function() {
         this.daysUrl = this.$resource('days{/day}');
         this.attendeesUrl = this.$resource('attendees{/id}');
+        this.currentUserUrl = this.$resource('users/current');
+        this.loginUrl = this.$resource('login');
+        this.logoutUrl = this.$resource('logout');
     },
 
     mounted: function() {
-        var ym = this.dateToString(this.state.date, true);
-        this.getSummary({start: ym, end: ym});
-        this.getDay(this.state.date);
+        this.getUserInfo();
+        var [year, month, day] = (this.$route.params.day || '').split('-');
+        year = parseInt(year);
+        month = parseInt(month) - 1;
+        day = parseInt(day);
+        if (!isNaN(year) && !isNaN(month) && !isNaN(day)) {
+            this.date = new Date(year, month, day);
+        }
+        if (!(this.date && !isNaN(this.date.getTime()))) {
+            this.date = new Date();
+        }
+        this.reload();
     },
 
     methods: {
+        focusToLoginForm() {
+            this.showLoginForm = true;
+            var that = this;
+            setTimeout(function() { that.$refs.usernameInput.$el.focus(); }, 400);
+        },
+
+        focusToPassword() {
+            this.$refs.passwordInput.$el.focus();
+        },
+
+        reload() {
+            var ym = this.dateToString(this.date, true);
+            this.getSummary({start: ym, end: ym});
+            this.getDay();
+        },
+
         dateToString(date, excludeDay) {
             var year = '' + date.getFullYear();
             var month = '' + (date.getMonth() + 1);
@@ -65,8 +105,6 @@ export default {
         },
 
         getSummary(params) {
-            console.log('getSummary');
-            console.log(params);
             if (!params) {
                 params = {};
             }
@@ -74,50 +112,73 @@ export default {
             this.daysUrl.query(params).then((response) => {
                 return response.json();
             }, (response) => {
-                alert('failed get resource');
+                alert('getSummary: failed to get resource');
             }).then((json) => {
-                console.log('summary data');
-                console.log(json);
+                this.daysSummary = json;
             });
         },
 
         getDay(day) {
-            console.log("getDay");
-            console.log(day);
-            if (day) {
+            if (day instanceof Date) {
+                this.date = day;
                 day = this.dateToString(day);
+            } else if (this.date && this.date instanceof Date) {
+                day = this.dateToString(this.date);
             } else {
-                day = this.state.day.day;
+                var today = new Date();
+                day = this.dateToString(today);
+                this.date = today;
             }
+            this.$router.push('/day/' + day);
             this.daysUrl.get({day: day}).then((response) => {
                 return response.json();
             }, (response) => {
-                alert('failed get resource');
-            }).then((json) => {
-                console.log('day data');
-                console.log(json);
-                this.state.day = json;
+                alert('getDay: failed to get resource');
+            }).then((dayData) => {
+                if (!dayData.day) {
+                    dayData.day = day;
+                }
+                this.day = dayData;
+            });
+        },
+
+        login() {
+            this.loginUrl.save({username: this.username, password: this.password}).then((response) => {
+                return response.json();
+            }, (response) => {
+                alert('login: failed to get resource');
+            }).then((data) => {
+                this.showLoginForm = false;
+                this.getUserInfo();
             });
         },
 
-        addAttendee(group, newAttendee) {
-            console.log(group);
-            console.log(newAttendee);
-            this.newAttendee = '';
-            this.attendeesUrl.save({day: this.state.day.day, group: group, name: newAttendee}).then((response) => {
+        logout() {
+            this.logoutUrl.get().then((response) => {
                 return response.json();
             }, (response) => {
-                alert('failed get resource');
+                alert('logout: failed to get resource');
             }).then((json) => {
-                console.log('attendee data');
-                console.log(json);
-                this.getDay();
+                this.loggedInUser = {};
+            });
+        },
+
+        getUserInfo(callback) {
+            this.currentUserUrl.get().then((response) => {
+                return response.json();
+            }, (response) => {
+                alert('getUserInfo: failed to get resource');
+            }).then((data) => {
+                this.loggedInUser = data || {};
+                if (callback) {
+                    callback(this.loggedInUser);
+                }
             });
         }
     },
 
     components: {
-        Datepicker
+        Datepicker, Group
     }
 }
 </script>
@@ -128,6 +189,22 @@ export default {
     -webkit-font-smoothing: antialiased;
     -moz-osx-font-smoothing: grayscale;
     color: #2c3e50;
-    margin-top: 60px;
+    margin-top: 0px;
+}
+
+#datepicker {
+    padding: 10px;
+}
+
+#panel .md-layout {
+    flex: initial;
+}
+
+#toolbar-title {
+    flex: 1;
+}
+
+#login-form {
+    width: 200px;
 }
 </style>

+ 71 - 0
src/Attendee.vue

@@ -0,0 +1,71 @@
+<template>
+    <md-list-item :key="attendee._id">
+        <md-icon>person</md-icon>
+        <span v-if="!edit">{{attendee.name}}</span>
+        <md-input-container md-inline v-if="edit">
+            <md-input @keyup.enter.native="updateAttendee()" v-model="attendee.name" ref="updateAttendeeName" />
+        </md-input-container>
+        <md-button class="md-icon-button md-list-action" @click="editAttendee()">
+            <md-icon>edit</md-icon>
+        </md-button>
+        <md-button class="md-icon-button md-list-action" @click="deleteAttendee()">
+            <md-icon>cancel</md-icon>
+        </md-button>
+    </md-list-item>
+</template>
+<script>
+
+export default {
+    props: {attendee: {default: {}}},
+
+    data: function () {
+        return {
+            edit: false
+        }
+    },
+
+    beforeCreate: function() {
+        this.attendeesUrl = this.$resource('attendees{/id}');
+    },
+
+    methods: {
+        editAttendee() {
+            this.edit = true;
+            // FIXME: it's so wrong it hurts, but any other attempt to set the focus
+            // failed, being called too early.  Also, I don't know how I can access
+            // Vue.nextTick from here.
+            var that = this;
+            setTimeout(function() { that.$refs.updateAttendeeName.$el.focus(); }, 400);
+        },
+
+        updateAttendee() {
+            this.attendeesUrl.update({id: this.attendee._id}, this.attendee).then((response) => {
+                return response.json();
+            }, (response) => {
+                alert('updateAttendee: failed to update resource');
+            }).then((json) => {
+                this.edit = false;
+                this.$emit('updated');
+            });
+        },
+
+        deleteAttendee() {
+            this.attendeesUrl.delete({id: this.attendee._id}).then((response) => {
+                return response.json();
+            }, (response) => {
+                alert('deleteAttendee: failed to delete resource');
+            }).then((json) => {
+                this.$emit('updated');
+            });
+        }
+    }
+};
+
+</script>
+<style>
+
+.md-list-item .md-list-item-holder>.md-icon:first-child {
+    margin-right: 16px;
+}
+
+</style>

+ 124 - 0
src/Group.vue

@@ -0,0 +1,124 @@
+<template>
+    <md-layout class="group-layout" md-col gutter="120" md-align="start">
+        <md-card v-if="!addNewGroup" md-with-hover @mouseenter.native="focusToNewAttendee()">
+            <md-card-header class="group-header">
+                <md-layout md-row>
+                    <div class="md-title">
+                        <md-icon class="group-icon">folder_open</md-icon>&nbsp;Group: {{ group.group }}
+                    </div>
+                </md-layout>
+            </md-card-header>
+            <md-card-content>
+                <md-list md-dense>
+                    <attendee v-for="attendee in group.attendees || []" :attendee="attendee" @updated="reload" />
+                    <md-list-item class="attendee-add">
+                        <md-icon>person_add</md-icon>
+                        <md-input-container class="new-attendee">
+                            <md-input ref="newAttendeeInput" @keyup.enter.native="addAttendee(group.group, newAttendee)" v-model="newAttendee" class="attendee-add-name" />
+                        </md-input-container>
+                    </md-list-item>
+                </md-list>
+            </md-card-content>
+        </md-card>
+        <md-card v-if="addNewGroup" md-with-hover @mouseenter.native="focusToNewGroup()" md-align="start">
+            <md-card-header class="new-group-header">
+                <div class="md-title">
+                    <md-input-container class="new-group">
+                        <md-icon>create_new_folder</md-icon>&nbsp;&nbsp;<md-input ref="newGroup" v-model="newGroup" @keyup.enter.native="focusToNewAttendee()" class="group-add-name" placeholder="new group" />
+                    </md-input-container>
+                </div>
+            </md-card-header>
+            <md-card-content>
+                <md-list v-show="newGroup">
+                    <md-list-item class="attendee-add">
+                        <md-icon>person_add</md-icon>
+                        <md-input-container md-inline>
+                            <md-input ref="newAttendeeInput" @keyup.enter.native="addAttendee(newGroup, newAttendee)" v-model="newAttendee" class="attendee-add-name" />
+                        </md-input-container>
+                    </md-list-item>
+                </md-list>
+            </md-card-content>
+        </md-card>
+    </md-layout>
+</template>
+<script>
+
+import Attendee from './Attendee';
+
+export default {
+    props: {group: {}, day: {}, addNewGroup: {default: false}},
+
+    data: function () {
+        return { newAttendee: '', newGroup: '' }
+    },
+
+    beforeCreate: function() {
+        this.attendeesUrl = this.$resource('attendees{/id}');
+    },
+
+    methods: {
+        reset() {
+            this.newAttendee = '';
+            this.newGroup = '';
+        },
+
+        reload() {
+            this.$emit('updated');
+            this.focusToNewAttendee();
+        },
+
+        focusToNewGroup() {
+            this.$refs.newGroup.$el.focus();
+        },
+
+        focusToNewAttendee() {
+            this.$refs.newAttendeeInput.$el.focus();
+        },
+
+        addAttendee(group, newAttendee) {
+            this.attendeesUrl.save({day: this.day, group: group, name: newAttendee}).then((response) => {
+                return response.json();
+            }, (response) => {
+                alert('addAttendee: failed to get resource');
+            }).then((json) => {
+                this.reset();
+                this.$emit('updated');
+            });
+        }
+    },
+
+    components: { Attendee }
+};
+
+</script>
+<style>
+.group-layout {
+    padding: 10px;
+}
+
+.new-group-header {
+    background-color: lightsteelblue;
+    padding-top: 0px !important;
+    padding-bottom: 0px !important;
+}
+
+.new-group-header .md-title {
+    margin-top: 0px !important;
+}
+
+.group-header {
+    background-color: lightblue;
+}
+
+.group-icon {
+    vertical-align: text-top;
+}
+
+.new-attendee {
+    width: 50px;
+}
+
+.new-group {
+    width: 260px;
+}
+</style>

+ 23 - 17
src/main.js

@@ -1,22 +1,28 @@
 // The Vue build version to load with the `import` command
 // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
-import Vue from 'vue'
-import App from './App'
-require("vue-resource")
-/*
-import 'jquery/dist/jquery.min.js'
-import 'materialize-css/bin/materialize.css'
-import 'materialize-css/bin/materialize.js'
-require("material-ui-vue")
-*/
-var VueResource = require("vue-resource");
-require("jquery");
+import Vue from 'vue';
+import VueRouter from 'vue-router';
+import VueResource from 'vue-resource';
+import VueMaterial from 'vue-material';
+import 'vue-material/dist/vue-material.css';
+import 'roboto-fontface/css/roboto/roboto-fontface.css';
+import 'material-design-icons/iconfont/material-icons.css';
+import jQuery from 'jquery';
+import App from './App';
 
+Vue.use(VueRouter);
 Vue.use(VueResource);
+Vue.use(VueMaterial);
 
-/* eslint-disable no-new  */
-new Vue({
-  el: '#app',
-  template: '<App/>',
-  components: { App }
-})
+var routes = [
+    {path: '/day/:day', component: App}
+];
+
+const router = new VueRouter({routes});
+
+var vue = new Vue({
+    el: '#app',
+    template: '<App/>',
+    router: router,
+    components: { App }
+});