Commit 56c9fcbb authored by vinh's avatar vinh

finalize

parent 7afc0148
...@@ -12,9 +12,12 @@ ...@@ -12,9 +12,12 @@
"bootstrap": "^5.2.3", "bootstrap": "^5.2.3",
"bootstrap-icons": "^1.10.5", "bootstrap-icons": "^1.10.5",
"bootstrap-vue": "^2.23.1", "bootstrap-vue": "^2.23.1",
"chart.js": "^4.3.0",
"core-js": "^3.8.3", "core-js": "^3.8.3",
"vue": "^3.2.47", "vue": "^3.2.47",
"vue-chartjs": "^5.2.0",
"vue-router": "^4.1.6", "vue-router": "^4.1.6",
"vuejs-paginate": "^2.1.0",
"vuex": "^4.0.2" "vuex": "^4.0.2"
}, },
"devDependencies": { "devDependencies": {
...@@ -1887,6 +1890,11 @@ ...@@ -1887,6 +1890,11 @@
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
"dev": true "dev": true
}, },
"node_modules/@kurkle/color": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz",
"integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw=="
},
"node_modules/@leichtgewicht/ip-codec": { "node_modules/@leichtgewicht/ip-codec": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz",
...@@ -4093,6 +4101,17 @@ ...@@ -4093,6 +4101,17 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/chart.js": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.3.0.tgz",
"integrity": "sha512-ynG0E79xGfMaV2xAHdbhwiPLczxnNNnasrmPEXriXsPJGjmhOBYzFVEsB65w2qMDz+CaBJJuJD0inE/ab/h36g==",
"dependencies": {
"@kurkle/color": "^0.3.0"
},
"engines": {
"pnpm": ">=7"
}
},
"node_modules/chokidar": { "node_modules/chokidar": {
"version": "3.5.3", "version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
...@@ -10841,6 +10860,15 @@ ...@@ -10841,6 +10860,15 @@
"@vue/shared": "3.2.47" "@vue/shared": "3.2.47"
} }
}, },
"node_modules/vue-chartjs": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-5.2.0.tgz",
"integrity": "sha512-d3zpKmGZr2OWHQ1xmxBcAn5ShTG917+/UCLaSpaCDDqT0U7DBsvFzTs69ZnHCgKoXT55GZDW8YEj9Av+dlONLA==",
"peerDependencies": {
"chart.js": "^4.1.1",
"vue": "^3.0.0-0 || ^2.7.0"
}
},
"node_modules/vue-eslint-parser": { "node_modules/vue-eslint-parser": {
"version": "8.3.0", "version": "8.3.0",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-8.3.0.tgz", "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-8.3.0.tgz",
...@@ -11091,6 +11119,11 @@ ...@@ -11091,6 +11119,11 @@
"integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==", "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==",
"dev": true "dev": true
}, },
"node_modules/vuejs-paginate": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/vuejs-paginate/-/vuejs-paginate-2.1.0.tgz",
"integrity": "sha512-gnwyXlmCiDOu9MLWxN5UJ4PGijKGNOMpHG8ujsrynCzTJljn/rp7Jq0WiDGDAMi5/u0AHuYIHhced+tUW4jblA=="
},
"node_modules/vuex": { "node_modules/vuex": {
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-4.0.2.tgz", "resolved": "https://registry.npmjs.org/vuex/-/vuex-4.0.2.tgz",
......
...@@ -12,9 +12,12 @@ ...@@ -12,9 +12,12 @@
"bootstrap": "^5.2.3", "bootstrap": "^5.2.3",
"bootstrap-icons": "^1.10.5", "bootstrap-icons": "^1.10.5",
"bootstrap-vue": "^2.23.1", "bootstrap-vue": "^2.23.1",
"chart.js": "^4.3.0",
"core-js": "^3.8.3", "core-js": "^3.8.3",
"vue": "^3.2.47", "vue": "^3.2.47",
"vue-chartjs": "^5.2.0",
"vue-router": "^4.1.6", "vue-router": "^4.1.6",
"vuejs-paginate": "^2.1.0",
"vuex": "^4.0.2" "vuex": "^4.0.2"
}, },
"devDependencies": { "devDependencies": {
......
<template> <template>
<div class="layout"> <div class="layout">
<HotelHeader /> <div class="header">
<HotelHeader />
</div>
<div class="body"> <div class="body">
<div v-if="shouldDisplaySidebar"> <div v-if="shouldDisplaySidebar !== '/login' && !shouldDisplaySidebar.includes('/reset-password/') && shouldDisplaySidebar !== '/register' && shouldDisplaySidebar !== '/verify-email' && width > 769"
<div class="sidebar" id="sidebar" v-if="width > 769"> >
<div class="sidebar">
<NavbarHotel /> <NavbarHotel />
</div> </div>
</div> </div>
...@@ -14,7 +17,6 @@ ...@@ -14,7 +17,6 @@
</div> </div>
</template> </template>
<script> <script>
import NavbarHotel from './components/NavbarHotel.vue' import NavbarHotel from './components/NavbarHotel.vue'
import HotelHeader from './components/HotelHeader.vue' import HotelHeader from './components/HotelHeader.vue'
...@@ -35,26 +37,32 @@ export default { ...@@ -35,26 +37,32 @@ export default {
} }
}, },
computed: { computed: {
shouldDisplaySidebar() { shouldDisplaySidebar() {
const currentView = this.$route.path; const currentView = this.$route.path;
return currentView !== '/login'; return currentView;
}, },
}, },
} }
</script> </script>
<style scoped> <style scoped>
@media (min-width: 769px) { @media (min-width: 769px) {
.header {}
.layout {
width:99vw;
min-height: 100vh;
}
.body { .body {
display: flex; display: flex;
width:100%;
} }
.layout { .layout {
height: 100vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.footer { .footer {
...@@ -62,12 +70,12 @@ export default { ...@@ -62,12 +70,12 @@ export default {
} }
.sidebar { .sidebar {
height: 100vh; position: sticky;
top: 0;
} }
.view { .view {
width: 100%; width: 100%;
height: 100vh;
} }
} }
......
<template>
<div class="table-box">
<table class="table">
<tr>
<th v-for="title in tableTitles" :key="title">{{ title }}</th>
</tr>
<tr v-for="datas, i in tableDatas" :key="i">
<td v-for="data, i in datas" :key="i">{{ data }}</td>
<td class="action-column">
<div v-if="actionSubmitInput != null">
<div>
<input type="text" :id="datas.id" name="fee" placeholder="Fee" class="fee">
<button type="submit" class="submit-button" @click="callParentSubmitInputFunction(datas.id)">
<i class="bi bi-currency-dollar"></i> {{ actionSubmitInput }}
</button>
</div>
</div>
<div v-if="actionModify != null">
<div>
<button class="modify-button" @click="callParentModifyFunction(datas.id, datas.booking_id)">
<i class="bi bi-pencil-square"></i> {{ actionModify }}
</button>
</div>
</div>
<div v-if="actionDetail != null">
<div>
<button class="detail-button"
@click="callParentDetailFunction(datas.id, datas.booking_id)">
<i class="bi bi-ticket-detailed"></i> {{ actionDetail }}
</button>
</div>
</div>
<div v-if="actionSubmit != null">
<div>
<button type="submit" class="submit-button" @click="callParentSubmitFunction(datas.id)">
<i class="bi bi-check-circle"></i> {{ actionSubmit }}
</button>
</div>
</div>
<div v-if="actionDanger != null">
<div>
<button class="danger-button" @click="callParentDangerFunction(datas.id)">
<i class="bi bi-trash"></i> {{ actionDanger }}
</button>
</div>
</div>
</td>
</tr>
</table>
</div>
</template>
<script>
export default {
name: 'DataTable',
props: {
tableDatas: {
type: Object,
required: true
},
tableTitles: {
type: Object,
required: true
},
actionDanger: {
type: String,
required: false,
default: null
},
actionModify: {
type: String,
required: false,
default: null
},
actionDetail: {
type: String,
required: false,
default: null
},
actionSubmit: {
type: String,
required: false,
default: null
},
actionSubmitInput: {
type: String,
required: false,
default: null
},
functionDetail: {
type: Function,
required: false,
},
functionModify: {
type: Function,
required: false,
},
functionDanger: {
type: Function,
required: false,
},
functionSubmit: {
type: Function,
required: false,
},
functionSubmitInput: {
type: Function,
required: false,
}
},
setup(props) {
const callParentDetailFunction = (param1, param2, param3) => {
props.functionDetail(param1, param2, param3);
}
const callParentModifyFunction = (param1, param2) => {
props.functionModify(param1, param2);
}
const callParentDangerFunction = (param1) => {
props.functionDanger(param1);
}
const callParentSubmitFunction = (param1) => {
props.functionSubmit(param1);
}
const callParentSubmitInputFunction = (param1) => {
const param2 = document.getElementById(param1).value;
console.log(param2)
props.functionSubmitInput(param1, param2);
}
return {
callParentDetailFunction,
callParentModifyFunction,
callParentDangerFunction,
callParentSubmitFunction,
callParentSubmitInputFunction
}
}
}
</script>
<style scoped>
.action-column {
display: flex;
}
</style>
<template>
<div class="detail-modal" id="detail-modal">
<div class="modal-content">
<div class="modal-header">
<span>Detail</span>
<span class="close" @click="closeModal()">&times;</span>
</div>
<div class="modal-body">
<div class="modal-icon">
<i class="bi bi-info-circle-fill"></i>
</div>
<div>
<div v-for="data, key in datas" :key="data.id"><span class="modal-title">{{ key }}: </span>{{ data }}</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'DetailInfo',
props: {
datas: {
type: Object,
}
},
methods: {
closeModal() {
document.getElementById('detail-modal').style.display = 'none'
}
}
}
</script>
<template> <template>
<div class="header"> <div class="header">
<h1 class="hotel-name">ABC Hotel</h1> <h1 class="hotel-name"><a href="/">ABC Hotel</a></h1>
<div v-if="width < 768"> <div v-if="width < 768 & currentView != '/login'">
<i class="bi bi-list" @click="showMenu()"></i> <i class="bi bi-list" @click="showMenu()"></i>
<div class="sidebar" id="sidebar"> <div class="side" id="side">
<NavbarHotel /> <NavbarHotel />
</div> </div>
</div> </div>
<router-link to="/register" class="register-box" v-show="currentView == '/login'">
Register
</router-link>
</div> </div>
</template> </template>
<script> <script>
import { ref } from 'vue'
import NavbarHotel from './NavbarHotel.vue' import NavbarHotel from './NavbarHotel.vue'
export default { export default {
...@@ -21,37 +23,64 @@ export default { ...@@ -21,37 +23,64 @@ export default {
}, },
setup() { setup() {
const width = ref(window.innerWidth)
const showMenu = () => { const showMenu = () => {
const sidebar = document.getElementById('sidebar') const sidebar = document.getElementById('side')
console.log('hello') console.log(sidebar.style.display)
if (sidebar.style.display === 'none') { if (sidebar.style.display === '') {
sidebar.style.display = 'block' sidebar.style.display = 'block'
} else { } else {
sidebar.style.display = 'none' sidebar.style.display = ''
} }
console.log(sidebar)
} }
return { return {
width, showMenu,
showMenu
} }
} },
computed: {
currentView() {
return this.$route.path
},
width() {
return window.innerWidth
}
},
} }
</script> </script>
<style scoped> <style scoped>
a {
text-decoration: none;
color: rgb(0, 0, 0);
}
@media (min-width: 769px) { @media (min-width: 769px) {
.header { .header {
width: 100%; width: 100%;
background-color: rgb(255, 255, 255); background-color: rgb(255, 255, 255);
padding: 10px; padding: 10px;
justify-content: space-between;
display: flex;
} }
.hotel-name { .hotel-name {
font-size: 30px; font-size: 30px;
} }
.register-box {
background-color: rgb(255, 255, 255);
border: 1px solid rgb(0, 0, 0);
border-radius: 5px;
padding: 5px;
font-size: 15px;
cursor: pointer;
}
.register-box:hover {
background-color: rgb(215, 215, 215);
}
} }
@media (max-width: 768px) { @media (max-width: 768px) {
...@@ -67,12 +96,19 @@ export default { ...@@ -67,12 +96,19 @@ export default {
font-size: 20px; font-size: 20px;
} }
.sidebar { .side {
display: none; display: none;
position: fixed; position: absolute;
right: 0; right: 0;
margin-top: 20px;
} }
.register-box {
background-color: rgb(255, 255, 255);
border: 1px solid rgb(0, 0, 0);
border-radius: 5px;
padding: 5px;
font-size: 15px;
cursor: pointer;
}
} }
</style> </style>
<template>
<div>
<div>
<Line :data="chartData" :options="chartOptions" />
</div>
</div>
</template>
<script>
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend,
BarController,
BarElement,
PieController,
ArcElement
} from 'chart.js'
import { Line } from 'vue-chartjs'
ChartJS.register(
CategoryScale,
LinearScale,
PointElement,
LineElement,
BarController,
BarElement,
PieController,
ArcElement,
Title,
Tooltip,
Legend
)
export default {
name: 'LineChart',
components: { Line },
props: {
chartData: {
type: Object,
required: true
},
chartOptions: {
type: Object,
required: true
}
},
}
</script>
\ No newline at end of file
<template> <template>
<div class="body"> <div class="sidebar" id="sidebar">
<div class="nav"> <div class="nav">
<div class="nav-content" :class="{ 'my-class': currentView == '/booking' }"> <router-link to="/" class="router-link" v-if="userRole == 'admin'">
<i class="bi bi-book-fill icon"> </i> <div class="nav-content" :class="{ 'active-route': currentView == '/' }">
<router-link to="/booking" class="router-link">View booking</router-link> <i class="bi bi-clipboard-fill icon"></i>
</div> Dashboard
<div class="nav-content" :class="{ 'active-route': currentView == '/checkin' }"> </div>
<i class="bi bi-bookmark-check-fill icon"></i> </router-link>
<router-link to="/checkin" class="router-link">View checkin</router-link> <router-link to="/booking" class="router-link">
</div> <div class="nav-content" :class="{ 'active-route': currentView == '/booking' }">
<div class="nav-content" :class="{ 'my-class': currentView == '/employee' }"> <i class="bi bi-book-fill icon"> </i>
<i class="bi bi-people-fill icon"></i> View booking
<router-link to="/employee" class="router-link">View employee</router-link> </div>
</div> </router-link>
<div class="nav-content" :class="{ 'my-class': currentView == '/room' }"> <router-link to="/checkin" class="router-link">
<i class="bi bi-bank2 icon"></i> <div class="nav-content" :class="{ 'active-route': currentView == '/checkin' }">
<router-link to="/room" class="router-link">View room</router-link> <i class="bi bi-bookmark-check-fill icon"></i>
</div> View checkin
<div class="nav-content" :class="{ 'my-class': currentView == '/' }"> </div>
<i class="bi bi-clipboard-fill icon"></i> </router-link>
<router-link to="/" class="router-link">Dashboard</router-link> <router-link to="/employee" class="router-link" v-if="userRole == 'admin'">
</div> <div class="nav-content" :class="{ 'active-route': currentView == '/employee' }">
<div class="nav-content" :class="{ 'my-class': currentView == '/user-list' }"> <i class="bi bi-people-fill icon"></i>
<i class="bi bi-person-badge-fill icon"></i> View employee
<router-link to="/user-list" class="router-link">User list</router-link> </div>
</div> </router-link>
<router-link to="/room" class="router-link">
<div class="nav-content" :class="{ 'active-route': currentView == '/room' }">
<i class="bi bi-bank2 icon"></i>
View room
</div>
</router-link>
<router-link to="/user-list" class="router-link" v-if="userRole == 'admin'">
<div class="nav-content" :class="{ 'active-route': currentView == '/user-list' }">
<i class="bi bi-person-badge-fill icon"></i>
User list
</div>
</router-link>
<router-link to="/guest" class="router-link" v-if="userRole == 'admin'">
<div class="nav-content" :class="{ 'active-route': currentView == '/guest' }">
<i class="bi bi-person-fill-check icon"></i>
Guest
</div>
</router-link>
</div> </div>
<div class="logout nav-content"> <div class="logout" @click="logout">
<i class="bi bi-box-arrow-right icon" @click="logout"></i> <i class="bi bi-box-arrow-right icon"> Logout</i>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import store from '../store'
export default { export default {
name: 'NavbarHotel', name: 'NavbarHotel',
//logout function props: {
closeMenu: {
type: Function,
required: false,
}
},
methods: { methods: {
logout() { logout() {
localStorage.removeItem('token'); localStorage.removeItem('token');
localStorage.removeItem('isLoggedIn'); localStorage.removeItem('isLoggedIn');
localStorage.removeItem('role');
this.$router.push('/login'); this.$router.push('/login');
} },
}, },
//get current route //get current route
computed: { computed: {
currentView() { currentView() {
return this.$route.path; return this.$route.path;
},
userRole() {
console.log(store.state.role);
return store.state.role;
} }
} }
} }
...@@ -54,10 +84,6 @@ export default { ...@@ -54,10 +84,6 @@ export default {
</script> </script>
<style scoped> <style scoped>
.title {
text-align: center;
}
.active-route { .active-route {
background-color: #c0bfbf; background-color: #c0bfbf;
color: #000; color: #000;
...@@ -65,35 +91,36 @@ export default { ...@@ -65,35 +91,36 @@ export default {
} }
.icon { .icon {
color: rgb(213, 213, 213); color: rgb(150, 150, 150);
font-size: 25px; font-size: 25px;
} }
.nav { .nav {
display: flex;
flex-direction: column;
background-color: rgb(248, 248, 248); background-color: rgb(248, 248, 248);
color: #f0f0f0; color: #f0f0f0;
height: 100%; min-height: 80vh;
gap: 30px;
width: 200px; width: 200px;
display: flex;
flex-direction: column;
} }
.nav-content { .nav-content {
padding: 10px;
padding-left: 20px; padding-left: 20px;
font-size: 15px; font-size: 15px;
display: flex; display: flex;
gap: 15px; gap: 15px;
padding: 15px;
min-height: 100px;
align-items: center;
} }
.nav-content:hover { .nav-content:not(.active-route):hover {
background-color: #c0bfbf; background-color: #eaeaea;
color: #000; color: #000;
font-size: 15px; font-size: 15px;
} }
.body { .sidebar {
height: 100%; height: 100%;
width: 2; width: 2;
display: flex; display: flex;
...@@ -107,15 +134,21 @@ export default { ...@@ -107,15 +134,21 @@ export default {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
width: 100%;
} }
.logout { .logout {
background-color: rgb(217, 84, 84);
cursor: pointer; cursor: pointer;
padding-left: 20px;
font-size: 15px;
display: flex;
gap: 15px;
padding: 15px;
min-height: 100px;
align-items: center;
} }
.logout:hover { .logout:hover {
background-color: rgb(247, 108, 105); background-color: rgb(247, 108, 105);
color: #fff; color: #fff;
} }
......
import axios from 'axios' import axios from 'axios'
axios.interceptors.request.use(
(config) => {
config.baseURL = 'http://localhost:8000/api'
config.headers.Accept = 'application/json'
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
const $axios = axios.create({ return config
baseURL: 'http://localhost:8000/api', },
headers: { (error) => {
'Accept': 'application/json', return Promise.reject(error)
'Authorization': `Bearer ${localStorage.getItem('token')}` }
} )
})
export default $axios axios.interceptors.response.use(
(response) => {
const newToken = response.data.access_token
if (newToken) {
localStorage.setItem('token', newToken)
}
return response
},
(error) => {
return Promise.reject(error)
}
)
export default axios
...@@ -6,6 +6,8 @@ import 'bootstrap-icons/font/bootstrap-icons.css' ...@@ -6,6 +6,8 @@ import 'bootstrap-icons/font/bootstrap-icons.css'
import axios from 'axios' import axios from 'axios'
import storeConfig from './store'; import storeConfig from './store';
import { createStore } from 'vuex'; import { createStore } from 'vuex';
import './style/CommonStyle.css'
const app = createApp(App) const app = createApp(App)
const store = createStore(storeConfig) const store = createStore(storeConfig)
...@@ -14,8 +16,5 @@ app.config.globalProperties.$axios = axios.create({ ...@@ -14,8 +16,5 @@ app.config.globalProperties.$axios = axios.create({
}) })
app.use(store) app.use(store)
app.use(router).mount('#app')
app.use(router).mount('#app')
...@@ -9,6 +9,9 @@ import ModifyView from '../views/ModifyView.vue' ...@@ -9,6 +9,9 @@ import ModifyView from '../views/ModifyView.vue'
import LoginView from '../views/LoginView.vue' import LoginView from '../views/LoginView.vue'
import register from '../views/RegisterView.vue' import register from '../views/RegisterView.vue'
import userList from '../views/UserListView.vue' import userList from '../views/UserListView.vue'
import verifyEmail from '../views/VerifyEmail.vue'
import resetPassword from '../views/ResetPasswordView.vue'
import guestView from '../views/GuestView.vue'
const routes = [ const routes = [
...@@ -61,15 +64,33 @@ const routes = [ ...@@ -61,15 +64,33 @@ const routes = [
path: '/user-list', path: '/user-list',
name: 'UserList', name: 'UserList',
component: userList component: userList
} },
{
path: '/verify-email',
name: 'VerifyEmail',
component: verifyEmail
},
{
path: '/reset-password/:token',
name: 'ResetPassword',
component: resetPassword
},
{
path: '/guest',
name: 'Guest',
component: guestView
},
] ]
routes.forEach(route => { routes.forEach(route => {
route.beforeEnter = (to, from, next) => { route.beforeEnter = (to, from, next) => {
const isAuthenticated = localStorage.getItem('isLoggedIn'); const isAuthenticated = localStorage.getItem('isLoggedIn');
if (to.path !== '/login' && to.path !== '/register' && !isAuthenticated) { const role = localStorage.getItem('role');
if (to.path !== '/login' && to.path !== '/register' && to.path !=='/verify-email' && !to.path.includes('/reset-password/') && !isAuthenticated) {
next('/login'); next('/login');
} else if (role !== 'admin' && to.path == '/' && to.path == '/user-list' && to.path == '/employee' && to.path == '/guest' ) {
next('/booking');
} else { } else {
next(); next();
} }
......
import router from "@/router"; import { createStore } from 'vuex';
import axios from "../config";
const storeConfig = { const storeConfig = {
state: { state: {
isLoggedIn: false, role: localStorage.getItem('role'),
token: null,
}, },
mutations: { mutations: {
login(state, token) { setRole(state) {
state.isLoggedIn = true; state.role = localStorage.getItem('role');
state.token = token;
localStorage.setItem('token', token)
localStorage.setItem('isLoggedIn', state.isLoggedIn)
}, },
logout(state) { removeRole(state) {
state.isLoggedIn = false state.role = null;
state.token = null
localStorage.removeItem('isLoggedIn')
localStorage.removeItem('token')
}, },
}, },
actions: {
async login({ commit }, { username, password }) {
const response = await axios.post('auth/login', {
username,
password
})
const token = response.data.access_token
console.log(token)
commit('login', token)
router.push( '/' )
},
logout({ commit }) {
commit("logout");
},
},
getters: {
isLoggedIn: (state) => state.isLoggedIn,
currentUser: (state) => state.user,
},
}; };
export default storeConfig; const store = createStore(storeConfig)
export default store;
.login-form-box {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
.login-button {
border: none;
background-color: #93e4c1;
color: white;
border-radius: 3px;
width: 250px;
height: 50px;
margin-top: 10px;
}
.error {
color: red;
font-weight: bold;
}
.detail-button {
border: none;
color: white;
border-radius: 3px;
background-color: #f5d442;
padding: 5px;
}
.detail-button:hover {
background-color: #caa811;
}
.input-form {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
background-color: rgb(255, 255, 255);
padding: 20px;
border-radius: 3px;
min-width: 350px;
min-height: 400px;
border: none;
border-radius: 3px;
box-shadow: 5px 5px 5px 5px #d5d5d5;
}
.input-title {
font-size: 30px;
font-weight: bold;
}
.input-label {
font-size: 15px;
color: rgb(164, 164, 164);
}
.input {
border: none;
background-color: rgb(240, 240, 240);
border-radius: 3px;
width: 250px;
height: 50px;
}
.table-box {
border: 2px rgb(222, 222, 222) solid;
border-radius: 13px;
padding: 5px;
white-space: nowrap;
overflow-x: scroll;
width: 100%;
}
th {
padding: 10px
}
td {
padding: 10px
}
.fee {
width: 100px;
border: none;
border-top-left-radius: 8px;
border-bottom-left-radius: 8px;
height: 33px;
}
tr:nth-child(even) {
background-color: rgb(224, 224, 224);
}
td:first-child {
border-top-left-radius: 15px;
border-bottom-left-radius: 15px;
}
td:last-child {
border-top-right-radius: 15px;
border-bottom-right-radius: 15px;
}
.submit-button {
border: none;
background-color: #93e4c1;
color: white;
padding: 5px;
border-radius: 3px;
}
.success {
color: rgb(79, 236, 55);
font-weight: bold;
}
.submit-button:hover {
background-color: #3baea0;
}
.w-100 {
width: 100%;
}
.danger-button {
border: none;
color: white;
border-radius: 3px;
background-color: #ff8585;
padding: 5px;
}
.danger-button:hover {
background-color: #ff5757;
}
.modify-button {
border: none;
padding: 5px;
color: white;
border-radius: 3px;
background: #18d6dc;
}
.modify-button:hover {
background: #3b77ef;
}
.modal-title {
font-weight: bold;
}
.modal-body {
padding: 15px;
display: flex;
}
.modal-icon {
font-size: 80px;
padding: 20px;
color: #f5d442;
}
.modal-header {
display: flex;
color: white;
font-size: 25px;
font-weight: bold;
width: 100%;
padding: 15px;
background-color: #f5d442;
}
.detail-modal {
display: none;
position: fixed;
/* Stay in place */
z-index: 1;
/* Sit on top */
left: 0;
top: 0;
width: 100%;
/* Full width */
height: 100%;
background-color: rgb(0, 0, 0);
/* Fallback color */
background-color: rgba(0, 0, 0, 0.4);
/* Black w/ opacity */
}
.modal-content {
background-color: #fefefe;
margin: 15% auto;
/* 15% from the top and centered */
border: 1px solid #888;
max-width: 800px;
min-height: 300px;
/* Could be more or less, depending on screen size */
}
.close {
color: #ffffff;
font-size: 20px;
display: flex;
flex-direction: column-reverse;
font-weight: bold;
height: 100%
}
.close:hover,
.close:focus {
color: black;
text-decoration: none;
cursor: pointer;
}
.title-box {
display: flex;
justify-content: space-between;
margin-bottom: 40px;
}
.title {
order: 0;
font-weight: bold;
}
/* loading effect */
.lds-ring {
width: 35px;
height: 35px;
}
.lds-ring div {
box-sizing: border-box;
position: absolute;
width: 25px;
height: 25px;
margin: 5px;
border: 5px solid #fff;
border-radius: 50%;
animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
border-color: #fff transparent transparent transparent;
}
.lds-ring div:nth-child(1) {
animation-delay: -0.45s;
}
.lds-ring div:nth-child(2) {
animation-delay: -0.3s;
}
.lds-ring div:nth-child(3) {
animation-delay: -0.15s;
}
@keyframes lds-ring {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
...@@ -4,52 +4,29 @@ ...@@ -4,52 +4,29 @@
<h1 class="title">Booking</h1> <h1 class="title">Booking</h1>
<div class="create-button-box"> <div class="create-button-box">
<router-link to="/create-booking"> <router-link to="/create-booking">
<button class="create-button">Create booking</button> <button class="modify-button">Create booking</button>
</router-link> </router-link>
</div> </div>
<br /> <br />
</div> </div>
<div class="table-box"> <DataTable :tableDatas="bookings" :tableTitles="tableTitles" :actionSubmit="'Checkin'" :actionDanger="'Delete'" :functionSubmit="checkin" :functionDanger="destroy"/>
<table class="table">
<tr class="">
<th>Booking ID</th>
<th>Guest Name</th>
<th>Guest Number</th>
<th>Room ID</th>
<th>Checked</th>
</tr>
<tr v-for="booking in bookings" :key="booking.id">
<td>{{ booking.id }}</td>
<td>{{ booking.guest_name }}</td>
<td>{{ booking.guest_number }}</td>
<td>{{ booking.room_id }}</td>
<td>{{ booking.checked }}</td>
<td>
<button @click="checkin(booking.id)" class="checkin-button">
<i class="bi bi-check-circle"></i> Checkin
</button>
</td>
<td>
<button @click="destroy(booking.id)" class="delete-button">
<i class="bi bi-trash"></i> Delete
</button>
</td>
</tr>
</table>
</div>
</div> </div>
</template> </template>
<script> <script>
import { defineComponent, ref } from "vue"; import { defineComponent, ref } from "vue";
import axios from '../config'; import axios from '../config';
import DataTable from '../components/DataTable.vue'
export default defineComponent({ export default defineComponent({
name: "BookingView", name: "BookingView",
components: {
DataTable,
},
setup() { setup() {
const bookings = ref([]); const bookings = ref([]);
const tableTitles = ref(['Booking id', 'Guest name', 'Guest number', 'Arrive date','Leave date', 'Room id', 'Checked'])
/** /**
* Get all bookings * Get all bookings
* *
...@@ -106,10 +83,12 @@ export default defineComponent({ ...@@ -106,10 +83,12 @@ export default defineComponent({
}; };
getBooking(); getBooking();
return { return {
checkin, checkin,
destroy, destroy,
bookings, bookings,
tableTitles
}; };
}, },
}); });
...@@ -132,30 +111,8 @@ export default defineComponent({ ...@@ -132,30 +111,8 @@ export default defineComponent({
padding: 5px; padding: 5px;
} }
.checkin-button {
border: none;
background: #93e4c1;
color: white;
border-radius: 20px;
}
.checkin-button:hover {
background: #3baea0;
}
.delete-button {
border: none;
border-radius: 20px;
background: #ff8585;
color: white;
}
.delete-button:hover {
background: #ff5757;
}
tr:nth-child(even) { tr:nth-child(even) {
background-color: rgb(224, 224, 224); background: rgb(224, 224, 224);
} }
td:first-child { td:first-child {
...@@ -168,12 +125,6 @@ export default defineComponent({ ...@@ -168,12 +125,6 @@ export default defineComponent({
border-bottom-right-radius: 15px; border-bottom-right-radius: 15px;
} }
.title-box {
display: flex;
justify-content: space-between;
margin-bottom: 40px;
}
.create-button-box { .create-button-box {
order: 2; order: 2;
} }
...@@ -190,11 +141,6 @@ export default defineComponent({ ...@@ -190,11 +141,6 @@ export default defineComponent({
background: #c9356c; background: #c9356c;
color: white; color: white;
} }
.title {
order: 0;
font-weight: bold;
}
} }
@media (max-width:768px) { @media (max-width:768px) {
...@@ -213,26 +159,6 @@ export default defineComponent({ ...@@ -213,26 +159,6 @@ export default defineComponent({
padding: 10px padding: 10px
} }
.table {
border: 2px rgb(222, 222, 222) solid;
border-radius: 13px;
padding: 5px;
white-space: nowrap;
}
.checkin-button {
border: none;
background: #93e4c1;
color: white;
border-radius: 20px;
}
.delete-button {
border: none;
border-radius: 20px;
background: #ff8585;
color: white;
}
.create-button { .create-button {
border: none; border: none;
...@@ -241,34 +167,22 @@ export default defineComponent({ ...@@ -241,34 +167,22 @@ export default defineComponent({
background: #ff347f; background: #ff347f;
color: white color: white
} }
.title-box {
display: flex;
justify-content: space-between;
padding:10px;
}
.create-button-box { .create-button-box {
order:1 order: 1
}
.title {
order: 0;
font-weight: bold;
} }
tr:nth-child(even) { tr:nth-child(even) {
background-color: rgb(224, 224, 224); background-color: rgb(224, 224, 224);
} }
td:first-child { td:first-child {
border-top-left-radius: 15px; border-top-left-radius: 15px;
border-bottom-left-radius: 15px; border-bottom-left-radius: 15px;
} }
td:last-child { td:last-child {
border-top-right-radius: 15px; border-top-right-radius: 15px;
border-bottom-right-radius: 15px; border-bottom-right-radius: 15px;
} }
} }
</style> </style>
...@@ -2,60 +2,47 @@ ...@@ -2,60 +2,47 @@
<div class="body"> <div class="body">
<div class="title-box"> <div class="title-box">
<h1 class="title">Checkin</h1><br> <h1 class="title">Checkin</h1><br>
<div class="">
<button class="modify-button" @click="exportData()">Export data</button>
</div>
</div> </div>
<div class="table-box"> <DataTable :tableDatas="checkins" :tableTitles="tableTitles" :actionModify="'Checkout'" :actionDetail="'View detail'"
<table class="table"> :actionDanger="'Delete'" :actionSubmitInput="'Add fee'" :functionModify="checkout" :functionDetail="viewDetail"
<tr class="table-column"> :functionDanger="destroy" :functionSubmitInput="addFee"></DataTable>
<th>Checkin ID</th> <DetailInfo :datas="CheckinDetail"></DetailInfo>
<th>Booking ID</th>
<th>Employee ID</th>
<th>Checkin Time</th>
<th>Checkout Time</th>
<th>Fee</th>
<th>Total Price</th>
</tr>
<tr v-for="checkin in checkins" :key="checkin.id">
<td>{{ checkin.id }}</td>
<td>{{ checkin.booking_id }}</td>
<td>{{ checkin.employee_id }}</td>
<td>{{ checkin.checkin_time }}</td>
<td>{{ checkin.checkout_time }}</td>
<td>{{ checkin.fee }}</td>
<td>{{ checkin.total_price }}</td>
<td>
<button @click="checkout(checkin.id, checkin.booking_id)" class="checkout-button">
<i class="bi bi-box-arrow-right"></i> Checkout
</button>
</td>
<td>
<button @click="destroy(checkin.id)" class="delete-button">
<i class="bi bi-trash"></i> Delete
</button>
</td>
<td>
<form @submit.prevent="addFee(checkin.id)" class="fee-box">
<input type="text" v-model="checkin.id" name="fee" id="fee" placeholder="Fee" class="fee">
<button type="submit" class="submit-button">
<i class="bi bi-currency-dollar"></i>Submit
</button>
</form>
</td>
</tr>
</table>
</div>
</div> </div>
</template> </template>
<script> <script>
import { defineComponent, ref } from 'vue' import { defineComponent, ref, } from 'vue'
import axios from '../config' import axios from '../config'
import DetailInfo from '../components/DetailInfo.vue'
import DataTable from '../components/DataTable.vue'
export default defineComponent({ export default defineComponent({
name: 'CheckinView', name: 'CheckinView',
components: {
DetailInfo,
DataTable
},
data() {
return {
fee: {}
}
},
setup() { setup() {
const checkins = ref([]); const tableTitles = ref([
'Checkin ID',
'Booking ID',
'Guest name',
'Checkin Time',
'Checkout Time',
'Fee',
'Total Price'
])
const checkins = ref();
/** /**
* Get all checkins * Get all checkins
* *
...@@ -109,44 +96,101 @@ export default defineComponent({ ...@@ -109,44 +96,101 @@ export default defineComponent({
getCheckin() getCheckin()
} }
const fee = ref('') /**
/** * View detail of a checkin
* Add fee to a checkin
* *
* @param {number} id * @param {number} employeeId
* @param {number} bookingId
* *
* @return {void} * @return {void}
*/ */
const addFee = async (id) => { const CheckinDetail = ref([]);
const viewDetail = async (checkinId, bookingId) => {
try {
console.log(checkinId, bookingId)
const response = await axios.post('checkin/checkin-detail', {
checkinId: checkinId,
bookingId: bookingId
})
CheckinDetail.value = {
'Guest name': response.data.guest_name,
'Guest number': response.data.guest_number,
'Booked time': response.data.created_at,
'Employee id': response.data.id,
'Employee name': response.data.name,
'Employee role': response.data.role,
}
console.log(CheckinDetail.value)
document.getElementById('detail-modal').style.display = 'block'
} catch (error) {
console.error(error.response.data)
}
}
const closeModal = () => {
document.getElementById('detail-modal').style.display = 'none'
}
/**
* Add fee to a checkin
*
* @param {number} id
*
* @return {void}
*/
const addFee = async (id, fee) => {
try { try {
console.log(id, fee.value); console.log(id, fee)
const response = await axios.post('checked/addFee', { const response = await axios.post('checked/addFee', {
id: id, id: id,
fee: fee.value fee: fee
}) })
console.log(response.data); console.log(response.data)
getCheckin() getCheckin()
} catch (error) { } catch (error) {
console.error(error.response.data) console.error(error.response.data)
} }
} }
const exportData = async () => {
try {
const response = await axios.get('checkin/export')
console.log(response.data)
} catch (error) {
console.error(error.response.data)
}
}
getCheckin() getCheckin()
return { return {
checkins, checkins,
exportData,
checkout, checkout,
destroy, destroy,
getCheckin,
viewDetail,
closeModal,
addFee, addFee,
fee tableTitles,
CheckinDetail
} }
},
}
}) })
</script> </script>
<style scoped> <style scoped>
@media (min-width: 768px) { .test {
max-width: 90%;
}
@media (min-width: 426px) {
.fee {
padding: 5px;
}
.body { .body {
background-color: rgb(240, 240, 240); background-color: rgb(240, 240, 240);
display: flex; display: flex;
...@@ -154,86 +198,7 @@ export default defineComponent({ ...@@ -154,86 +198,7 @@ export default defineComponent({
padding: 20px; padding: 20px;
border-radius: 3px; border-radius: 3px;
height: 100%; height: 100%;
} width: 86vw;
.table-box {
border: 2px rgb(222, 222, 222) solid;
border-radius: 13px;
padding: 5px;
}
th {
padding: 10px
}
.checkout-button {
border: none;
border-radius: 20px;
background: #8bffff;
color: white
}
.checkout-button:hover {
background: #82acff;
}
.delete-button {
border: none;
border-radius: 20px;
background: #ff8585;
color: white;
}
.delete-button:hover {
background: #ff5757;
}
.fee-box {
display: flex;
}
.fee {
width: 100px;
border: none;
border-top-left-radius: 8px;
border-bottom-left-radius: 8px;
}
.submit-button {
border: none;
background: #93e4c1;
color: white;
border-top-right-radius: 8px;
border-bottom-right-radius: 8px;
}
.submit-button:hover {
background: #3baea0;
}
tr:nth-child(even) {
background-color: rgb(224, 224, 224);
}
td:first-child {
border-top-left-radius: 15px;
border-bottom-left-radius: 15px;
}
td:last-child {
border-top-right-radius: 15px;
border-bottom-right-radius: 15px;
}
.title-box {
display: flex;
justify-content: space-between;
margin-bottom: 40px;
}
.title {
order: 0;
font-weight: bold;
} }
} }
...@@ -245,50 +210,6 @@ export default defineComponent({ ...@@ -245,50 +210,6 @@ export default defineComponent({
flex-direction: column; flex-direction: column;
min-height: 100vh; min-height: 100vh;
} }
.table-box {
overflow: scroll;
}
.table {
border: 2px rgb(222, 222, 222) solid;
border-radius: 13px;
padding: 5px;
white-space: nowrap;
}
th {
padding: 10px
}
td {
padding: 10px
}
.checkout-button {
border: none;
border-radius: 20px;
background: #8bffff;
}
tr:nth-child(even) {
background-color: rgb(224, 224, 224);
}
td:first-child {
border-top-left-radius: 15px;
border-bottom-left-radius: 15px;
}
td:last-child {
border-top-right-radius: 15px;
border-bottom-right-radius: 15px;
}
.delete-button {
border: none;
border-radius: 20px;
background: #ff8585;
}
.submit-button { .submit-button {
border: none; border: none;
...@@ -303,16 +224,7 @@ export default defineComponent({ ...@@ -303,16 +224,7 @@ export default defineComponent({
border: none; border: none;
border-top-left-radius: 8px; border-top-left-radius: 8px;
border-bottom-left-radius: 8px; border-bottom-left-radius: 8px;
} padding: 5px;
.title-box {
padding:10px;
font-weight: bold;
}
.title {
order: 0;
font-weight: bold;
} }
} }
</style> </style>
...@@ -5,30 +5,64 @@ ...@@ -5,30 +5,64 @@
</div> </div>
<div class="input-box"> <div class="input-box">
<div class="input-box-content"> <div class="input-box-content">
<form @submit.prevent="addBooking" class=""> <form @submit.prevent="checkAvailability()" class="">
<div class="input-title-box"> <div class="input-title-box">
Enter information Enter information
</div> </div>
<div class=""> <div class="error">
<label for="guest_name" class="">Guest Name</label> {{ errorValue }}
<div class=""> </div>
<input type="text" id="guest_name" v-model="guest_name" name="guest_name" class="input"> <div class="detail-modal" id="modal">
<div class="modal-content h-400px">
<div class="modal-header">
<span>Create booking</span>
<span class="close" @click="closeModal">&times;</span>
</div>
<div class="file-box mt-4">
<form class="booking-form" @submit.prevent="addBooking">
<div class="">
<label for="guest_name" class="">Guest Name</label>
<div class="">
<input type="text" id="guest_name" v-model="guest_name" name="guest_name" class="input">
</div>
</div>
<div class="">
<label for="guest_number" class="">Guest Number</label>
<div class="">
<input type="text" id="guest_number" v-model="guest_number" name="guest_number" class="input">
</div>
</div>
<div>
<label for="room" class="">Room</label>
<div>
<select class="input" name="room" id="room" v-model="room_id">
<option v-for="room in availableRoom" :key="room.id" :value="room.id" >
{{ room.name }}
</option>
</select>
</div>
<button type="submit" class="submit-button h-50px mt-3 w-100">
<i class="bi bi-check-circle"></i> Submit
</button>
</div>
</form>
</div>
</div> </div>
</div> </div>
<div class=""> <div class="">
<label for="guest_number" class="">Guest Number</label> <label for="arrive_date" class="">Arrive date</label>
<div class=""> <div class="">
<input type="text" id="guest_number" v-model="guest_number" name="guest_number" class="input"> <input type="date" id="arrive_date" v-model="arrive_date" name="arrive_date" class="input">
</div> </div>
</div> </div>
<div class=""> <div class="">
<label for="room_id" class="">Room ID</label> <label for="leave_date" class="">Leave date</label>
<div class=""> <div class="">
<input type="text" id="room_id" v-model="room_id" name="room_id" class="input"> <input type="date" id="leave_date" v-model="leave_date" name="leave_date" class="input">
</div> </div>
</div> </div>
<button type="submit" class="submit-button"> <button type="submit" class="submit-button w-100 h-50px mt-3">
<i class="bi bi-check-circle"></i> Submit <i class="bi bi-check-circle"></i> Check
</button> </button>
</form> </form>
</div> </div>
...@@ -44,41 +78,123 @@ import router from '@/router' ...@@ -44,41 +78,123 @@ import router from '@/router'
export default defineComponent({ export default defineComponent({
name: "CreateBookingView", name: "CreateBookingView",
setup() { setup() {
const guest_name = ref(""); const guest_name = ref('')
const guest_number = ref(""); const guest_number = ref('')
const room_id = ref(""); const arrive_date = ref('')
const leave_date = ref('')
const room_id = ref('')
const deviceWidth = ref(window.innerWidth)
const availableRoom = ref('')
const err = ref('')
/** /**
* Add booking * Add booking
* *
* @return {void} * @return {void}
*/ */
const addBooking = async () => { const addBooking = async () => {
console.log(guest_name.value, guest_number.value, room_id.value);
try { try {
const response = await axios.post("create-booking", { const userId = (await axios.post('auth/me')).data.id
await axios.post('booking/create-booking', {
guest_name: guest_name.value, guest_name: guest_name.value,
guest_number: guest_number.value, guest_number: guest_number.value,
room_id: room_id.value arrive_date: arrive_date.value,
}); room_id: room_id.value,
console.log(response.data); leave_date: leave_date.value,
alert("Create booking successfully"); guest_id: userId
router.push({ name: "Booking" }); })
router.push({ name: 'Booking' })
} catch (error) {
err.value = error.response.data.message
console.log(err.value)
} }
catch (error) { }
alert(error.response.data.message);
const showAvailableRoom = async (id) => {
try {
let response = await axios.post('booking/show-available-room', {
id
})
availableRoom.value = response.data
} catch (error) {
err.value = error.response.data.message
console.log(err.value)
} }
}; }
const openModal = () => {
document.getElementById('modal').style.display = 'block'
}
const closeModal = () => {
document.getElementById('modal').style.display = 'none'
}
const checkAvailability = async () => {
try {
let response = await axios.post('booking/check-availability', {
arrive_date: arrive_date.value,
leave_date: leave_date.value
})
showAvailableRoom(response.data)
openModal()
} catch (error) {
err.value = error.response.data.message
console.log(err.value)
}
}
return { return {
guest_name, guest_name,
guest_number, guest_number,
arrive_date,
room_id, room_id,
addBooking leave_date,
}; addBooking,
deviceWidth,
openModal,
closeModal,
err,
checkAvailability,
availableRoom
}
}, },
computed: {
errorValue() {
return this.err;
}
}
}) })
</script> </script>
<style scoped> <style scoped>
.h-50px {
height: 50px;
}
.h-400px {
height: 400px;
}
.booking-form {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100%;
}
.modal-header {
display: flex;
color: white;
font-size: 25px;
font-weight: bold;
width: 100%;
padding: 15px;
background-color: #18d6dc;
}
@media (min-width: 769px) { @media (min-width: 769px) {
.input-title-box { .input-title-box {
font-size: 30px; font-size: 30px;
...@@ -86,6 +202,11 @@ export default defineComponent({ ...@@ -86,6 +202,11 @@ export default defineComponent({
margin-bottom: 40px; margin-bottom: 40px;
} }
.error {
color: red;
font-weight: bold;
}
.body { .body {
background-color: rgb(240, 240, 240); background-color: rgb(240, 240, 240);
display: flex; display: flex;
...@@ -95,6 +216,7 @@ export default defineComponent({ ...@@ -95,6 +216,7 @@ export default defineComponent({
height: 100%; height: 100%;
} }
.input-box-content { .input-box-content {
display: flex; display: flex;
background-color: white; background-color: white;
...@@ -123,23 +245,20 @@ export default defineComponent({ ...@@ -123,23 +245,20 @@ export default defineComponent({
width: 300px; width: 300px;
height: 50px; height: 50px;
} }
.submit-button {
border: none;
background: #93e4c1;
color: white;
border-radius: 3px;
width: 300px;
height: 50px;
}
.submit-button:hover {
background: #3baea0;
}
} }
@media (max-width: 768px) { @media (max-width: 768px) {
.modal-content {
background-color: #fefefe;
margin: 15% auto;
/* 15% from the top and centered */
border: 1px solid #888;
max-width: 800px;
min-height: 500px;
/* Could be more or less, depending on screen size */
}
.input-title-box { .input-title-box {
font-size: 30px; font-size: 30px;
font-weight: bold; font-weight: bold;
...@@ -153,17 +272,6 @@ export default defineComponent({ ...@@ -153,17 +272,6 @@ export default defineComponent({
min-height: 100vh; min-height: 100vh;
} }
.title-box {
display: flex;
justify-content: space-between;
padding:10px;
}
.title {
order: 0;
font-weight: bold;
}
.input-box { .input-box {
margin-top: 40px; margin-top: 40px;
padding: 20px; padding: 20px;
...@@ -180,10 +288,15 @@ export default defineComponent({ ...@@ -180,10 +288,15 @@ export default defineComponent({
justify-content: center; justify-content: center;
border: none; border: none;
border-radius: 3px; border-radius: 3px;
padding:50px; padding: 50px;
box-shadow: 5px 5px 5px 5px #d5d5d5; box-shadow: 5px 5px 5px 5px #d5d5d5;
} }
.error {
color: red;
font-weight: bold;
}
.input { .input {
border: none; border: none;
background-color: rgb(240, 240, 240); background-color: rgb(240, 240, 240);
...@@ -200,6 +313,7 @@ export default defineComponent({ ...@@ -200,6 +313,7 @@ export default defineComponent({
border-radius: 3px; border-radius: 3px;
width: 250px; width: 250px;
height: 50px; height: 50px;
margin-top: 20px;
} }
} }
......
...@@ -3,26 +3,6 @@ ...@@ -3,26 +3,6 @@
<div class="title-box"> <div class="title-box">
<h1 class="title">Dashboard</h1> <h1 class="title">Dashboard</h1>
</div> </div>
<div>
<div class="table-box">
<table class="table">
<tr>
<th>Booking ID</th>
<th>Guest Name</th>
<th>Guest Number</th>
<th>Room ID</th>
<th>Total Price</th>
</tr>
<tr v-for="guest in guestInfos" :key="guest.id">
<td>{{ guest.id }}</td>
<td>{{ guest.guest_name }}</td>
<td>{{ guest.guest_number }}</td>
<td>{{ guest.room_id }}</td>
<td>{{ guest.checkin.total_price }}</td>
</tr>
</table>
</div>
</div>
<div class="summary-box"> <div class="summary-box">
<div class="summary"> <div class="summary">
<div class="icon-box"> <div class="icon-box">
...@@ -58,78 +38,187 @@ ...@@ -58,78 +38,187 @@
</div> </div>
</div> </div>
</div> </div>
<div class="chart">
<h2>Total booking of each month this year</h2>
<div>
<LineChart v-if="loaded" :chartData="LineChartData" :chartOptions="LineChartOptions" />
</div>
<h2>Interest of each month this year</h2>
<div>
<LineChart v-if="loaded" :chartData="BarChartData" :chartOptions="BarChartOptions" />
</div>
</div>
<div>
<h2>Income after expense</h2>
<div>
<LineChart v-if="loaded" :chartData="PieChartData" :chartOptions="PieChartOptions" />
</div>
</div>
</div> </div>
</template> </template>
<script> <script>
import { defineComponent } from 'vue'; import { defineComponent, ref } from 'vue';
import axios from '../config'
import LineChart from '../components/LineChart.vue'
export default defineComponent({ export default defineComponent({
name: 'DashboardView', name: 'DashboardView',
components: {
LineChart
},
data() { data() {
return { return {
guestInfos: [], loaded: false,
bookingInfos: [] LineChartData: {
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
datasets: [
{
label: 'Booking',
data: [],
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 1,
},
],
},
LineChartOptions: {
responsive: true,
maintainAspectRatio: true,
scales: {
x: {
title: {
display: true,
text: 'Booking'
},
ticks: {
beginAtZero: true
}
},
y: {
min: 0,
ticks: {
stepSize: 1
},
title: {
display: true,
text: 'Month'
}
},
},
elements: {
point: {
backgroundColor: 'red'
}
}
},
BarChartData: {
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
datasets: [{
label: 'Income',
data: [],
backgroundColor: 'rgba(54, 162, 235, 0.2)',
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 1,
type: 'bar'
}]
},
BarChartOptions: {
scales: {
x: {
title: {
display: true,
},
ticks: {
beginAtZero: true
}
},
y: {
title: {
display: true,
text: 'Month'
}
}
}
},
PieChartData: {
labels: ['Income', 'Employee salary', 'expense', 'tax'],
datasets: [{
backgroundColor: ['#41B883', '#E46651', '#00D8FF', 'rgb(114,64,109)'],
data: [],
type: 'pie',
labels: ['Income', 'Employee salary', 'expense', 'tax'],
}]
},
//make legend show all 4 labels
PieChartOptions: {
responsive: true,
maintainAspectRatio: true,
}
} }
}, },
async mounted() {
let results = await this.$axios.get('dashboard')
this.guestInfos = results.data.guestInfos setup() {
this.bookingInfos = results.data.bookingInfos const tableTitles = ref(['ID', 'Guest Name', 'Guest Number', 'Room ID', 'Checkin time'])
const guestInfos = ref([])
const checkinToday = ref()
const bookingInfos = ref([])
const incomeAndSalary = ref([])
const getData = async () => {
try {
let results = await axios.get('dashboard')
guestInfos.value = results.data.guestInfos
bookingInfos.value = results.data.bookingInfos
} catch (error) {
console.log(error)
}
}
getData()
return {
guestInfos,
bookingInfos,
incomeAndSalary,
tableTitles,
checkinToday
}
}, },
async mounted() {
this.loaded = false
try {
const bookingThisYear = await axios.get('booking-this-year')
this.LineChartData.datasets[0].data = bookingThisYear.data
const interestThisYear = await axios.get('interest-this-year')
this.BarChartData.datasets[0].data = interestThisYear.data
const IncomeAndSalary = await axios.get('income-this-month')
this.PieChartData.datasets[0].data = IncomeAndSalary.data
this.loaded = true
} catch (e) {
console.error(e)
}
}
}) })
</script> </script>
<style scoped> @media (min-width: 769px) { <style scoped>
@media (min-width: 426px) {
.body { .body {
background-color: rgb(240, 240, 240); background-color: rgb(240, 240, 240);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 20px; padding: 20px;
border-radius: 3px; border-radius: 3px;
height: 100%; min-height: 100%;
}
.table-box {
border: 2px rgb(222, 222, 222) solid;
border-radius: 15px;
padding: 5px;
}
tr:nth-child(even) {
background-color: rgb(224, 224, 224);
}
td:first-child {
border-top-left-radius: 15px;
border-bottom-left-radius: 15px;
}
td:last-child {
border-top-right-radius: 15px;
border-bottom-right-radius: 15px;
}
.title-box {
display: flex;
justify-content: space-between;
margin-bottom: 40px;
}
.title {
order: 0;
font-weight: bold;
} }
.summary-box { .summary-box {
display: flex; display: flex;
justify-content: space-evenly; justify-content: space-evenly;
margin-top: 100px; margin-top: 10px;
} }
.summary { .summary {
...@@ -143,6 +232,7 @@ export default defineComponent({ ...@@ -143,6 +232,7 @@ export default defineComponent({
.summary-title { .summary-title {
font-size: 10px; font-size: 10px;
color: rgb(181, 181, 181); color: rgb(181, 181, 181);
text-align: center;
} }
.icon-box { .icon-box {
...@@ -158,6 +248,13 @@ export default defineComponent({ ...@@ -158,6 +248,13 @@ export default defineComponent({
color: rgb(255, 0, 0); color: rgb(255, 0, 0);
} }
.chart {
display: flex;
flex-direction: column;
gap: 20px;
margin-top: 50px;
}
.safe-icon { .safe-icon {
color: rgb(252, 255, 64); color: rgb(252, 255, 64);
} }
...@@ -165,10 +262,11 @@ export default defineComponent({ ...@@ -165,10 +262,11 @@ export default defineComponent({
.summary-value { .summary-value {
font-size: 30px; font-size: 30px;
font-weight: bold; font-weight: bold;
text-align: center;
} }
} }
@media (max-width: 768px) { @media (max-width: 425px) {
.body { .body {
background-color: rgb(240, 240, 240); background-color: rgb(240, 240, 240);
display: flex; display: flex;
...@@ -176,50 +274,6 @@ export default defineComponent({ ...@@ -176,50 +274,6 @@ export default defineComponent({
min-height: 100vh; min-height: 100vh;
} }
.table-box {
overflow: scroll;
}
th {
padding: 10px
}
.table {
border: 2px rgb(222, 222, 222) solid;
border-radius: 13px;
padding: 5px;
white-space: nowrap;
}
td {
padding: 10px
}
.title-box {
display: flex;
justify-content: space-between;
padding: 10px;
}
.title {
order: 0;
font-weight: bold;
}
tr:nth-child(even) {
background-color: rgb(224, 224, 224);
}
td:first-child {
border-top-left-radius: 15px;
border-bottom-left-radius: 15px;
}
td:last-child {
border-top-right-radius: 15px;
border-bottom-right-radius: 15px;
}
.summary-box { .summary-box {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
......
...@@ -3,40 +3,31 @@ ...@@ -3,40 +3,31 @@
<div class="title-box"> <div class="title-box">
<h1 class="title">Employee</h1> <h1 class="title">Employee</h1>
</div> </div>
<div class="table-box"> <DataTable :tableDatas="employees" :tableTitles="tableTitles" />
<table class="table">
<tr class="">
<th>Employee ID</th>
<th>Employee Name</th>
<th>Role</th>
<th>Status</th>
<th>Shift</th>
<th>Day Off</th>
<th>Salary</th>
</tr>
<tr v-for="employee in employees" :key="employee.id">
<td>{{ employee.id }}</td>
<td>{{ employee.name }}</td>
<td>{{ employee.role }}</td>
<td>{{ employee.status }}</td>
<td>{{ employee.shift }}</td>
<td>{{ employee.day_off }}</td>
<td>{{ employee.salary }}</td>
</tr>
</table>
</div>
</div> </div>
</template> </template>
<script> <script>
import { defineComponent } from 'vue' import { defineComponent } from 'vue'
import DataTable from '@/components/DataTable.vue'
export default defineComponent({ export default defineComponent({
name: 'EmployeeView', name: 'EmployeeView',
components: {
DataTable
},
data() { data() {
return { return {
employees: [] employees: [],
tableTitles: [
'ID',
'Name',
'Role',
'Shift',
'Day off',
'Salary',
'Status'
]
} }
}, },
...@@ -58,36 +49,6 @@ export default defineComponent({ ...@@ -58,36 +49,6 @@ export default defineComponent({
height: 100%; height: 100%;
} }
.table-box {
border: 2px rgb(222, 222, 222) solid;
border-radius: 15px;
padding: 5px;
}
tr:nth-child(even) {
background-color: rgb(224, 224, 224);
}
td:first-child {
border-top-left-radius: 15px;
border-bottom-left-radius: 15px;
}
td:last-child {
border-top-right-radius: 15px;
border-bottom-right-radius: 15px;
}
.title-box {
display: flex;
justify-content: space-between;
margin-bottom: 40px;
}
.title {
order: 0;
font-weight: bold;
}
} }
@media (max-width: 768px) { @media (max-width: 768px) {
...@@ -97,49 +58,5 @@ export default defineComponent({ ...@@ -97,49 +58,5 @@ export default defineComponent({
flex-direction: column; flex-direction: column;
min-height: 100vh; min-height: 100vh;
} }
.table-box {
overflow: scroll;
}
th {
padding: 10px
}
.table {
border: 2px rgb(222, 222, 222) solid;
border-radius: 13px;
padding: 5px;
white-space: nowrap;
}
td {
padding: 10px
}
.title-box {
display: flex;
justify-content: space-between;
padding:10px;
}
.title {
order: 0;
font-weight: bold;
}
tr:nth-child(even) {
background-color: rgb(224, 224, 224);
}
td:first-child {
border-top-left-radius: 15px;
border-bottom-left-radius: 15px;
}
td:last-child {
border-top-right-radius: 15px;
border-bottom-right-radius: 15px;
}
} }
</style> </style>
<template>
<div class="body">
<div class="title-box">
<h1 class="title">Guest</h1>
</div>
<div>
<DataTable :tableDatas="checkinToday" :tableTitles="tableTitles"></DataTable>
</div>
</div>
</template>
<script>
import DataTable from '../components/DataTable.vue'
import { ref } from 'vue'
import axios from '../config'
export default {
name: 'GuestView',
components: {
DataTable
},
setup() {
const checkinToday = ref([]);
const tableTitles = ['Guest id', 'Guest name', 'Total booking', 'Email address', 'Booking this month', 'Joined date']
/**
* Get all bookings
*
* @return {void}
*/
const getGuestStatistic = async () => {
const response = await axios.get('guest-statistic')
checkinToday.value = response.data
console.log(response.data)
}
getGuestStatistic()
return {
checkinToday,
tableTitles
}
}
}
</script>
<style scoped>
@media (min-width: 769px) {
.body {
background-color: rgb(240, 240, 240);
display: flex;
flex-direction: column;
padding: 20px;
border-radius: 3px;
height: 100%;
}
}
</style>
\ No newline at end of file
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>
<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'
export default {
name: 'HomeView',
components: {
HelloWorld
}
}
</script>
<template> <template>
<div class="body"> <div class="body">
<form @submit.prevent="submitForm" class="login-form-box"> <form @submit.prevent="submitForm" class="login-form-box">
<div class="login-form"> <div class="input-form">
<div class="input-title">Login</div> <div class="input-title">Login</div>
<div class="error">
{{ errors }}
</div>
<div class="input-box"> <div class="input-box">
<div for="username" class="input-label">Username</div> <div for="username" class="input-label">Username</div>
<input type="text" id="username" name="username" v-model="username" class="input"> <input type="text" id="username" name="username" v-model="username" class="input">
...@@ -11,108 +14,186 @@ ...@@ -11,108 +14,186 @@
<div for="password" class="input-label">Password</div> <div for="password" class="input-label">Password</div>
<input type="password" id="password" name="password" v-model="password" class="input"> <input type="password" id="password" name="password" v-model="password" class="input">
</div> </div>
<button type="submit" @click="submitForm" class="submit-button">Login</button> <button type="submit" class="login-button">Login</button>
<div>{{ error }}</div> <div class="forgot-password" @click="openImportModal">Forgot password?</div>
</div> </div>
</form> </form>
<div>
</div>
<div class="detail-modal" id="import-modal">
<div class="modal-content">
<div class="modal-header">
<span>Forgot password</span>
<span class="close" @click="closeImportModal">&times;</span>
</div>
<div class="file-box">
<div class="success" :class="{'hidden': success}">Please check your email</div>
<form @submit.prevent="forgotPassword()">
<input type="email" class="form-control form-control-xlg" placeholder="Enter your email" v-model="email">
<button type="submit" class="submit-button w-100 mt-5">
<div class="flex">
<div class="lds-ring loading" :class="{ 'active': isPending }">
<!-- :class="{ 'active': isPending }" -->
<div></div>
<div></div>
<div></div>
<div></div>
</div>
<div class="d-flex align-items-center">
Submit
</div>
</div>
</button>
</form>
</div>
</div>
</div>
</div> </div>
</template> </template>
<script> <script>
import { defineComponent, ref } from 'vue' import { defineComponent, ref } from 'vue'
import storeConfig from '../store'; import store from '../store';
import { createStore } from 'vuex'; // import { createStore } from 'vuex';
import axios from '../config'
import router from '../router'
export default defineComponent({ export default defineComponent({
name: 'LoginView', name: 'LoginView',
setup() { setup() {
const store = createStore(storeConfig) // const store = createStore(storeConfig)
const username = ref(''); const username = ref('');
const password = ref(''); const password = ref('');
const error = ref(''); const errors = ref('');
/** /**
* Submit form * Submit form
* *
* @return {void} * @return {void}
*/ */
const submitForm = () => { const submitForm = async () => {
store.dispatch('login', { username: username.value, password: password.value }) try {
let response = await axios.post('auth/login', {
username: username.value,
password: password.value
})
localStorage.setItem('token', response.data.access_token)
localStorage.setItem('isLoggedIn', true)
router.push('/')
let response2 = await axios.post('auth/me')
const role = response2.data.role
localStorage.setItem('role', role)
store.commit('setRole')
} catch (error) {
errors.value = error.response.data.error
}
}
const email = ref('');
const isPending = ref(true);
const success = ref(true)
const forgotPassword = async () => {
try {
isPending.value = false
let response = await axios.post('/forgot-password', {
email: email.value,
})
console.log(response.data);
} catch (error) {
errors.value = error.response.data.error
} finally {
isPending.value = true
success.value = false
}
}
const openImportModal = () => {
document.getElementById('import-modal').style.display = 'block'
}
const closeImportModal = () => {
document.getElementById('import-modal').style.display = 'none'
} }
return { return {
username, username,
password, password,
error, errors,
submitForm, submitForm,
openImportModal,
closeImportModal,
email,
forgotPassword,
isPending,
success
}; };
}, },
}) })
</script> </script>
<style scoped> <style scoped>
.body { .active {
background-color: rgb(240, 240, 240); visibility: hidden;
display: flex;
flex-direction: column;
padding: 100px;
border-radius: 3px;
min-height: 100%;
} }
.login-form-box { .flex {
display: flex; display: flex;
justify-content: center; gap: 60px;
align-items: center; flex-direction: row-reverse;
height: 100%;
} }
.login-form { .forgot-password:hover {
display: flex; cursor: pointer;
flex-direction: column; text-decoration: underline blue;
justify-content: space-between; color: blue;
align-items: center;
background-color: rgb(255, 255, 255);
padding: 20px;
border-radius: 3px;
min-width: 500px;
min-height: 400px;
border:none;
border-radius: 3px;
box-shadow: 5px 5px 5px 5px #d5d5d5;
} }
.submit-button { .forgot-password {
border: none; font-size: 12px;
background: #93e4c1; margin-top: 10px;
color: white; color: rgb(156, 156, 156);
border-radius: 3px;
width: 300px;
height: 50px;
} }
.submit-button:hover { .hidden {
background: #3baea0; display: none;
} }
.input-title { .file-box {
font-size: 30px; width: 50%;
font-weight: bold; padding: 30px;
align-self: center;
display: flex;
flex-direction: column;
gap: 50px;
} }
.input-label { .modal-content {
font-size: 15px; background-color: #fefefe;
color: rgb(164, 164, 164); margin: 15% auto;
/* 15% from the top and centered */
border: 1px solid #888;
max-width: 600px;
min-height: 300px;
/* Could be more or less, depending on screen size */
} }
.input { .modal-header {
border: none; display: flex;
color: white;
font-size: 25px;
font-weight: bold;
width: 100%;
padding: 15px;
background-color: #18d6dc;
}
.body {
background-color: rgb(240, 240, 240); background-color: rgb(240, 240, 240);
display: flex;
flex-direction: column;
padding: 100px;
border-radius: 3px; border-radius: 3px;
width: 300px; min-height: 100vh;
height: 50px;
} }
</style> </style>
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
<span> <span>
Room Name Room Name
</span> </span>
<button type="button" @click="showInput('name')" class="show-button">Modify</button> <button type="button" @click="showInput('name')" class="modify-button">Modify</button>
</label> </label>
<input type="text" id="name" name="name" v-model="name" class="input"> <input type="text" id="name" name="name" v-model="name" class="input">
</div> </div>
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
<span> <span>
First Image First Image
</span> </span>
<button type="button" @click="showInput('image_first')" class="show-button">Modify</button> <button type="button" @click="showInput('image_first')" class="modify-button">Modify</button>
</label> </label>
<input type="file" ref="fileInput" @change="imageFirst" id="image_first" name="image_first" <input type="file" ref="fileInput" @change="imageFirst" id="image_first" name="image_first"
class="input"> class="input">
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
<span> <span>
Second Image Second Image
</span> </span>
<button type="button" @click="showInput('image_second')" class="show-button">Modify</button> <button type="button" @click="showInput('image_second')" class="modify-button">Modify</button>
</label> </label>
<input type="file" ref="fileInput" @change="imageSecond" id="image_second" name="image_second" <input type="file" ref="fileInput" @change="imageSecond" id="image_second" name="image_second"
class="input"> class="input">
...@@ -40,7 +40,7 @@ ...@@ -40,7 +40,7 @@
<span> <span>
Third Image Third Image
</span> </span>
<button type="button" @click="showInput('image_third')" class="show-button">Modify</button> <button type="button" @click="showInput('image_third')" class="modify-button">Modify</button>
</label> </label>
<input type="file" ref="fileInput" @change="imageThird" id="image_third" name="image_third" <input type="file" ref="fileInput" @change="imageThird" id="image_third" name="image_third"
class="input"> class="input">
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
<span> <span>
Type Type
</span> </span>
<button type="button" @click="showInput('type')" class="show-button">Modify</button> <button type="button" @click="showInput('type')" class="modify-button">Modify</button>
</label> </label>
<input type="text" id="type" name="type" v-model="type" class="input"> <input type="text" id="type" name="type" v-model="type" class="input">
</div> </div>
...@@ -59,7 +59,7 @@ ...@@ -59,7 +59,7 @@
<span> <span>
Hour Price Hour Price
</span> </span>
<button type="button" @click="showInput('hour_price')" class="show-button">Modify</button> <button type="button" @click="showInput('hour_price')" class="modify-button">Modify</button>
</label> </label>
<input type="text" id="hour_price" name="hour_price" v-model="hour_price" class="input"> <input type="text" id="hour_price" name="hour_price" v-model="hour_price" class="input">
</div> </div>
...@@ -68,7 +68,7 @@ ...@@ -68,7 +68,7 @@
<span> <span>
Day Price Day Price
</span> </span>
<button type="button" @click="showInput('day_price')" class="show-button">Modify</button> <button type="button" @click="showInput('day_price')" class="modify-button">Modify</button>
</label> </label>
<input type="text" id="day_price" name="day_price" v-model="day_price" class="input"> <input type="text" id="day_price" name="day_price" v-model="day_price" class="input">
</div> </div>
...@@ -79,7 +79,7 @@ ...@@ -79,7 +79,7 @@
<span> <span>
Size Size
</span> </span>
<button type="button" @click="showInput('size')" class="show-button">Modify</button> <button type="button" @click="showInput('size')" class="modify-button">Modify</button>
</label> </label>
<input type="number" id="size" name="size" v-model="size" class="input"> <input type="number" id="size" name="size" v-model="size" class="input">
</div> </div>
...@@ -88,7 +88,7 @@ ...@@ -88,7 +88,7 @@
<span> <span>
Balcony Balcony
</span> </span>
<button type="button" @click="showInput('balcony')" class="show-button">Modify</button> <button type="button" @click="showInput('balcony')" class="modify-button">Modify</button>
</label> </label>
<div> <div>
<div class=""> <div class="">
...@@ -104,7 +104,7 @@ ...@@ -104,7 +104,7 @@
<span> <span>
View View
</span> </span>
<button type="button" @click="showInput('view')" class="show-button">Modify</button> <button type="button" @click="showInput('view')" class="modify-button">Modify</button>
</label> </label>
<input type="text" id="view" name="view" v-model="view" class="input"> <input type="text" id="view" name="view" v-model="view" class="input">
</div> </div>
...@@ -113,7 +113,7 @@ ...@@ -113,7 +113,7 @@
<span> <span>
Description Description
</span> </span>
<button type="button" @click="showInput('description')" class="show-button">Modify</button> <button type="button" @click="showInput('description')" class="modify-button">Modify</button>
</label> </label>
<input type="text" id="description" name="description" v-model="description" class="input"> <input type="text" id="description" name="description" v-model="description" class="input">
</div> </div>
...@@ -122,7 +122,7 @@ ...@@ -122,7 +122,7 @@
<span> <span>
Smoking Smoking
</span> </span>
<button type="button" @click="showInput('smoking')" class="show-button">Modify</button> <button type="button" @click="showInput('smoking')" class="modify-button">Modify</button>
</label> </label>
<div> <div>
<div class=""> <div class="">
...@@ -138,7 +138,7 @@ ...@@ -138,7 +138,7 @@
<span> <span>
Floor Floor
</span> </span>
<button type="button" @click="showInput('floor')" class="show-button">Modify</button> <button type="button" @click="showInput('floor')" class="modify-button">Modify</button>
</label> </label>
<input type="number" id="floor" name="floor" v-model="floor" class="input"> <input type="number" id="floor" name="floor" v-model="floor" class="input">
</div> </div>
...@@ -147,7 +147,7 @@ ...@@ -147,7 +147,7 @@
<span> <span>
Bathtub Bathtub
</span> </span>
<button type="button" @click="showInput('bathtub')" class="show-button">Modify</button> <button type="button" @click="showInput('bathtub')" class="modify-button">Modify</button>
</label> </label>
<div> <div>
<div class=""> <div class="">
...@@ -160,7 +160,7 @@ ...@@ -160,7 +160,7 @@
</div> </div>
</div> </div>
<div class=""> <div class="">
<button type="submit" class="submit-button"> <button type="submit" class="submit-button w-100" :class="{ 'mt-3': width <= 425 }">
<i class="bi bi-send">submit</i> <i class="bi bi-send">submit</i>
</button> </button>
</div> </div>
...@@ -179,6 +179,7 @@ export default defineComponent({ ...@@ -179,6 +179,7 @@ export default defineComponent({
name: 'ModifyView', name: 'ModifyView',
setup() { setup() {
const width = ref(window.innerWidth)
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
const id = route.params.id const id = route.params.id
...@@ -244,18 +245,20 @@ export default defineComponent({ ...@@ -244,18 +245,20 @@ export default defineComponent({
imageFirst, imageFirst,
imageSecond, imageSecond,
imageThird, imageThird,
width
} }
}, },
methods: { methods: {
//display input field on click //display input field on click
showInput(input) { showInput(input) {
console.log('okay') console.log(input)
const id = ref(document.getElementById(input)); const id = ref(document.getElementById(input));
if (id.value.style.display === "none") { console.log(id.value.style)
if (id.value.style.display === "") {
id.value.style.display = "block"; id.value.style.display = "block";
} else { } else {
id.value.style.display = "none"; id.value.style.display = "";
} }
} }
} }
...@@ -263,6 +266,16 @@ export default defineComponent({ ...@@ -263,6 +266,16 @@ export default defineComponent({
</script> </script>
<style scoped> <style scoped>
.form-box {
border: 2px rgb(222, 222, 222) solid;
border-radius: 15px;
padding: 5px;
}
.input-box {
border-bottom: 1px solid rgb(178, 178, 178);
}
@media (min-width: 769px) { @media (min-width: 769px) {
.body { .body {
background-color: rgb(240, 240, 240); background-color: rgb(240, 240, 240);
...@@ -273,18 +286,6 @@ export default defineComponent({ ...@@ -273,18 +286,6 @@ export default defineComponent({
min-height: 100%; min-height: 100%;
} }
.title {
order: 0;
font-weight: bold;
}
.title-box {
display: flex;
justify-content: space-between;
margin-bottom: 40px;
}
.input { .input {
display: none; display: none;
border: none; border: none;
...@@ -296,12 +297,6 @@ export default defineComponent({ ...@@ -296,12 +297,6 @@ export default defineComponent({
justify-content: space-between; justify-content: space-between;
} }
.form-box {
border: 2px rgb(222, 222, 222) solid;
border-radius: 15px;
padding: 5px;
}
.column { .column {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
...@@ -318,32 +313,7 @@ export default defineComponent({ ...@@ -318,32 +313,7 @@ export default defineComponent({
color: rgb(178, 178, 178); color: rgb(178, 178, 178);
} }
.input-box {
border-bottom: 1px solid rgb(178, 178, 178);
}
.show-button {
border: none;
background-color: #8bffff;
cursor: pointer;
border-radius: 5px;
padding: 5px;
margin-left: 10px;
}
.show-button:hover {
background-color: #1aeded;
color: white;
}
.submit-button {
border: none;
background-color: rgb(89, 229, 98);
cursor: pointer;
border-radius: 5px;
padding: 5px;
margin-left: 10px;
}
} }
@media (max-width:768px) { @media (max-width:768px) {
...@@ -356,43 +326,12 @@ export default defineComponent({ ...@@ -356,43 +326,12 @@ export default defineComponent({
min-height: 100%; min-height: 100%;
} }
.title {
order: 0;
font-weight: bold;
}
.title-box {
display: flex;
justify-content: space-between;
margin-bottom: 40px;
}
.form-box {
border: 2px rgb(222, 222, 222) solid;
border-radius: 15px;
padding: 5px;
}
.input { .input {
display: none; display: none;
border: none; border: none;
border-radius: 5px; border-radius: 5px;
width: 100%; width: 100%;
} }
.input-box {
border-bottom: 1px solid rgb(178, 178, 178);
}
.show-button {
border: none;
background-color: #8bffff;
cursor: pointer;
border-radius: 5px;
padding: 5px;
margin-left: 10px;
}
.label { .label {
padding: 10px; padding: 10px;
border-radius: 5px; border-radius: 5px;
...@@ -403,14 +342,5 @@ export default defineComponent({ ...@@ -403,14 +342,5 @@ export default defineComponent({
color: rgb(178, 178, 178); color: rgb(178, 178, 178);
} }
.submit-button {
border: none;
background-color: rgb(89, 229, 98);
cursor: pointer;
border-radius: 5px;
padding: 5px;
width: 100%;
margin-top: 10px;
}
} }
</style> </style>
<template> <template>
<div class="container"> <div class="body">
<div class="row justify-content-center"> <form @submit.prevent="register" class="login-form-box">
<div class="col-lg-6"> <div class="input-form">
<h1 class="text-center mb-5">Register</h1> <div class="input-title">Register</div>
<form @submit.prevent="register" class="needs-validation" novalidate> <div class="success" :class="{'hidden': success}">Registered successfully, please verify your email</div>
<div class="form-floating mb-3"> <div class="input-box">
<input type="text" class="form-control" id="username" name="username" v-model="username" <div for="username" class="input-label">Username</div>
placeholder="Username" required> <input type="text" id="username" name="username" v-model="username" class="input">
<label for="username">Username</label> </div>
<div class="invalid-feedback"> <div class="input-box">
Please enter a valid username. <div for="email" class="input-label">Email</div>
<input type="email" class="input" id="email" name="email" v-model="email">
</div>
<div class="input-box-radio">
<div for="role" class="input-label">Role</div>
<div class="radio">
<div>
<input type="radio" id="admin" name="role" value="admin" v-model="role">
<label for="admin" class="input-label">Admin</label>
</div> </div>
</div> <div>
<div class="form-floating mb-3"> <input type="radio" id="employee" name="role" value="employee" v-model="role">
<input type="password" class="form-control" id="password" name="password" v-model="password" <label for="employee" class="input-label">Employee</label>
placeholder="Password" required>
<label for="password">Password</label>
<div class="invalid-feedback">
Please enter a valid password.
</div> </div>
</div> </div>
<div class="form-floating mb-3"> </div>
<input type="password" class="form-control" id="confirm_password" name="confirm_password" <div class="input-box">
v-model="confirm_password" placeholder="Confirm Password" required> <div for="password" class="input-label">Password</div>
<label for="confirm_password">Confirm Password</label> <input type="password" id="password" name="password" v-model="password" class="input">
<div class="invalid-feedback"> </div>
Please confirm your password. <div class="input-box">
<div for="confirm-password" class="input-label">Confirm password</div>
<input type="password" class="input" id="confirm_password" name="confirm_password" v-model="confirm_password">
</div>
<button type="submit" class="login-button">
<div class="flex">
<div class="lds-ring loading" :class="{ 'active': isPending }" >
<!-- :class="{ 'active': isPending }" -->
<div></div>
<div></div>
<div></div>
<div></div>
</div> </div>
</div> <div class="d-flex align-items-center">
<div class="form-floating mb-3"> Register
<input type="email" class="form-control" id="email" name="email" v-model="email" placeholder="Email" required>
<label for="email">Email</label>
<div class="invalid-feedback">
Please enter a valid email address.
</div> </div>
</div> </div>
<button type="submit" class="btn btn-primary">Submit</button> </button>
<div>{{ errors }}</div>
</form>
</div> </div>
</div> </form>
</div> </div>
</template> </template>
<script> <script>
import axios from '../config' import axios from '../config'
import { ref, defineComponent } from 'vue' import { ref, defineComponent } from 'vue'
import storeConfig from '../store';
import { createStore } from 'vuex';
export default defineComponent({ export default defineComponent({
name: 'RegisterView', name: 'RegisterView',
setup() { setup() {
const store = createStore(storeConfig)
const username = ref('') const username = ref('')
const password = ref('') const password = ref('')
const confirm_password = ref('') const confirm_password = ref('')
const email = ref('') const email = ref('')
const errors = ref('') const errors = ref('')
const role = ref('')
const isPending = ref(true)
const success = ref(true)
/** /**
* Register * Register
* *
...@@ -67,19 +75,23 @@ export default defineComponent({ ...@@ -67,19 +75,23 @@ export default defineComponent({
*/ */
const register = async () => { const register = async () => {
try { try {
isPending.value = false
const response = await axios.post('register', { const response = await axios.post('register', {
username: username.value, username: username.value,
password: password.value, password: password.value,
confirm_password: confirm_password.value, confirm_password: confirm_password.value,
email: email.value email: email.value,
role: role.value,
}) })
store.dispatch('login', { username: username.value, password: password.value })
console.log(response.data); console.log(response.data);
success.value = false
} }
catch (error) { catch (error) {
errors.value = error.response.data.message errors.value = error.response.data.message
console.log(errors.value); alert(errors.value);
} finally {
isPending.value = true
} }
} }
...@@ -89,8 +101,53 @@ export default defineComponent({ ...@@ -89,8 +101,53 @@ export default defineComponent({
confirm_password, confirm_password,
email, email,
errors, errors,
role,
isPending,
success,
register, register,
}; };
} }
}) })
</script> </script>
<style scoped>
.hidden {
display: none;
}
.active {
visibility: hidden;
}
.loading {
font-size: 1px;
}
.body {
background-color: rgb(240, 240, 240);
display: flex;
flex-direction: column;
padding: 100px;
border-radius: 3px;
min-height: 100vh;
}
.input-box-radio {
display: flex;
flex-direction: column;
align-items: baseline;
width: 250px;
}
.flex {
display: flex;
gap: 60px;
flex-direction: row-reverse;
}
.radio {
display: flex;
gap: 50px;
}
</style>
<template>
<form @submit.prevent="resetPassword()">
<h2>Reset Password</h2>
<div class="success">
{{ noti }}
</div>
<div>
<label for="email">Email</label>
<input type="email" id="email" name="email" v-model="email" required>
</div>
<div>
<label for="password">New Password</label>
<input type="password" id="password" name="password" v-model="password" required>
</div>
<div>
<label for="confirm-password">Confirm Password</label>
<input type="password" id="confirm-password" name="confirm-password" v-model="confirmPassword" required>
</div>
<div>
<input type="hidden" name="token" :value="token">
</div>
<button type="submit">Reset Password</button>
</form>
</template>
<script>
import router from "@/router";
import axios from "../config";
import { ref } from "vue";
export default {
name: "ResetPasswordView",
setup() {
//get token from url
const token = window.location.href.split("/").pop();
console.log(token);
const email = ref("");
const password = ref("");
const confirmPassword = ref("");
const timer = ref(6)
const noti = ref('')
const resetPassword = async () => {
try {
const response = await axios.post("reset-password", {
token: token,
email: email.value,
password: password.value,
password_confirmation: confirmPassword.value,
});
console.log(response.data);
} catch (error) {
console.log(error.response.data.message);
} finally {
const interval = setInterval(() => {
timer.value -= 1; // Decrease countdown by 1 every second
noti.value = "Change password successfully, redirect to login page in " + timer.value + " seconds";
if (timer.value === 0) {
clearInterval(interval); // Stop the interval when countdown reaches 0
router.push('/'); // Redirect after countdown reaches 0
}
}, 1000);
}
}
return {
token,
email,
password,
confirmPassword,
resetPassword,
timer,
noti
}
}
}
</script>
<style scoped>
form {
width: 700px;
margin: auto;
padding: 20px;
border: 0.5px solid black;
border-radius: 5px;
display: flex;
flex-direction: column;
gap: 20px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
}
h2 {
text-align: center;
font-weight: bold;
}
div {
margin-bottom: 10px;
}
label {
display: block;
}
input[type="email"],
input[type="password"] {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
}
button[type="submit"] {
display: block;
width: 100%;
padding: 10px;
background-color: rgb(258, 187, 0);
color: white;
border: none;
cursor: pointer;
}
</style>
\ No newline at end of file
...@@ -2,69 +2,157 @@ ...@@ -2,69 +2,157 @@
<div class="body"> <div class="body">
<div class="title-box"> <div class="title-box">
<h1 class="title">Room</h1> <h1 class="title">Room</h1>
<div class="">
<button class="modify-button" @click="openImportModal">Import room</button>
<div class="detail-modal" id="import-modal">
<div class="modal-content">
<div class="modal-header">
<span>Detail</span>
<span class="close" @click="closeImportModal">&times;</span>
</div>
<div class="file-box">
<input class="form-control form-control-lg" type="file" :v-model="fileInput" @change="handleFileUpload" />
<button @click="uploadFile" class="submit-button">Upload</button>
</div>
</div>
</div>
</div>
</div> </div>
<div class="table-box"> <div>
<table class="table"> <DataTable :tableDatas="tableData" :tableTitles="tableTitles" :actionModify="'Modify'" :actionDetail="'View detail'"
<tr class=""> :functionDetail="viewRoomDetail" :functionModify="modifyRoom" :key="key"></DataTable>
<th>Room ID</th> <div>
<th>Room Name</th> <DetailInfo :datas="roomInfo"></DetailInfo>
<th>Type</th> </div>
<th>Hour Price</th>
<th>Day Price</th>
<th>Status</th>
<th>Size</th>
<th>Balcony</th>
<th>View</th>
<th>Smoking</th>
<th>Floor</th>
<th>Bathtub</th>
<th>Actions</th>
</tr>
<tr v-for="room in rooms" :key="room.id">
<td>{{ room.id }}</td>
<td>{{ room.name }}</td>
<td>{{ room.type }}</td>
<td>{{ room.hour_price }}</td>
<td>{{ room.day_price }}</td>
<td>{{ room.status }}</td>
<td>{{ room.size }}</td>
<td>{{ room.balcony }}</td>
<td>{{ room.view }}</td>
<td>{{ room.smoking }}</td>
<td>{{ room.floor }}</td>
<td>{{ room.bathtub }}</td>
<td>
<router-link :to="{ name: 'Modify', params: { id: room.id } }">
<button class="modify-button">
<i class="bi bi-pencil-square"></i> Modify
</button>
</router-link>
</td>
</tr>
</table>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { defineComponent } from 'vue'; import DataTable from '../components/DataTable.vue'
export default defineComponent({ import { ref} from 'vue'
name: 'RoomView', import axios from '../config'
import DetailInfo from '../components/DetailInfo.vue'
import router from '../router'
export default {
name: 'TestingView',
components: {
DataTable,
DetailInfo
},
data() { setup() {
return { const tableData = ref({})
rooms: [] const roomInfo = ref({})
const modalStatus = ref(false)
// const fileInput = ref(null)
const file = ref(null)
const tableTitles = ref([
'Room ID',
'Room Name',
'Type',
'Hour price',
'Day price',
'Size',
])
const getRooms = async () => {
let results = await axios.get('room')
tableData.value = results.data
}
const modifyRoom = (id) => {
//router push to modify room
router.push('/modify/' + id)
}
const handleFileUpload = (event) => {
file.value = event.target.files[0];
}
const viewRoomDetail = async (id) => {
let response = await axios.post('room-detail', {
id: id
})
roomInfo.value = {
'Id': response.data.id,
'Name': response.data.name,
'Type': response.data.type,
'Hour price': response.data.hour_price,
'Day price': response.data.day_price,
'Status': response.data.status,
'Size': response.data.size,
'Balcony': response.data.balcony,
'View': response.data.view,
'Smoking': response.data.smoking,
'Floor': response.data.floor,
'Bathtub': response.data.bathtub,
}
document.getElementById('detail-modal').style.display = 'block'
}
const openImportModal = () => {
document.getElementById('import-modal').style.display = 'block'
}
const closeImportModal = () => {
document.getElementById('import-modal').style.display = 'none'
}
const uploadFile = async () => {
let formData = new FormData();
formData.append('file', file.value);
await axios.post('/room/storeRoomFile', formData)
.then(response => {
console.log(response.data);
})
.catch(error => {
console.log(error);
}).finally(() => {
closeImportModal()
});
getRooms()
} }
},
async mounted() { getRooms()
let results = await this.$axios.get('room') return {
this.rooms = results.data tableData,
tableTitles,
viewRoomDetail,
handleFileUpload,
roomInfo,
modalStatus,
closeImportModal,
modifyRoom,
openImportModal,
uploadFile,
}
} }
}) }
</script> </script>
<style scoped> <style scoped>
.file-box {
width:50%;
padding:30px;
align-self: center;
display:flex;
flex-direction: column;
gap:50px;
}
.modal-header {
display: flex;
color: white;
font-size: 25px;
font-weight: bold;
width: 100%;
padding: 15px;
background-color: #18d6dc;
}
@media (min-width: 769px) { @media (min-width: 769px) {
.body { .body {
background-color: rgb(240, 240, 240); background-color: rgb(240, 240, 240);
...@@ -72,50 +160,9 @@ export default defineComponent({ ...@@ -72,50 +160,9 @@ export default defineComponent({
flex-direction: column; flex-direction: column;
padding: 20px; padding: 20px;
border-radius: 3px; border-radius: 3px;
height: 100%; min-height: 100%;
}
.table-box {
border: 2px rgb(222, 222, 222) solid;
border-radius: 15px;
padding: 5px;
}
tr:nth-child(even) {
background-color: rgb(224, 224, 224);
}
td:first-child {
border-top-left-radius: 15px;
border-bottom-left-radius: 15px;
}
td:last-child {
border-top-right-radius: 15px;
border-bottom-right-radius: 15px;
}
.title-box {
display: flex;
justify-content: space-between;
margin-bottom: 40px;
}
.title {
order: 0;
font-weight: bold;
}
.modify-button {
border: none;
border-radius: 20px;
background: #8bffff;
color: white
} }
.modify-button:hover {
background: #82acff;
}
} }
@media (max-width:768px) { @media (max-width:768px) {
...@@ -125,56 +172,5 @@ export default defineComponent({ ...@@ -125,56 +172,5 @@ export default defineComponent({
flex-direction: column; flex-direction: column;
min-height: 100vh; min-height: 100vh;
} }
.table-box {
overflow: scroll;
}
th {
padding: 10px
}
.table {
border: 2px rgb(222, 222, 222) solid;
border-radius: 13px;
padding: 5px;
white-space: nowrap;
}
td {
padding: 10px
}
.title-box {
display: flex;
justify-content: space-between;
padding:10px;
}
.title {
order: 0;
font-weight: bold;
}
.modify-button {
border: none;
border-radius: 20px;
background: #8bffff;
color: white
}
tr:nth-child(even) {
background-color: rgb(224, 224, 224);
}
td:first-child {
border-top-left-radius: 15px;
border-bottom-left-radius: 15px;
}
td:last-child {
border-top-right-radius: 15px;
border-bottom-right-radius: 15px;
}
} }
</style> </style>
<template> <template>
<div class="body"> <div class="body">
<div class="title-box"> <div class="title-box">
<h1 class=title>List of user:</h1> <h1 class=title>List of user</h1>
</div> </div>
<div> <div>
<div class="table-box"> <DataTable :tableDatas="users" :tableTitles="tableTitles" :actionDanger="'Delete'" :functionDanger="deleteUser"></DataTable>
<table class="table">
<tr>
<th>ID</th>
<th>Username</th>
<th>Role</th>
<th>Actions</th>
</tr>
<tr v-for="user in users" :key="user.id">
<td>{{ user.id }}</td>
<td>{{ user.username }}</td>
<td>{{ user.role }}</td>
<td>
<button @click="deleteUser(user.id)" class="delete-button">
<i class="bi bi-trash"></i> Delete
</button>
</td>
</tr>
</table>
</div>
</div> </div>
<!-- <table class="table table-striped">
<thead>
<tr>
<th>ID</th>
<th>Username</th>
<th>Role</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="user in users" :key="user.id">
<td>{{ user.id }}</td>
<td>{{ user.username }}</td>
<td>{{ user.role }}</td>
<td>
<button @click="deleteUser(user.id)" class="btn btn-danger">
<i class="bi bi-trash"></i> Delete
</button>
</td>
</tr>
</tbody>
</table> -->
</div> </div>
</template> </template>
<script> <script>
import { defineComponent, ref } from 'vue' import { defineComponent, ref } from 'vue'
import axios from '../config' import axios from '../config'
import DataTable from '../components/DataTable.vue'
export default defineComponent({ export default defineComponent({
name: 'UserListView', name: 'UserListView',
components: {
DataTable
},
setup() { setup() {
const users = ref([]); const users = ref([]);
const tableTitles = ref(['User id', 'Username', 'email', 'User role'])
/** /**
* Get all users * Get all users
...@@ -95,7 +59,8 @@ export default defineComponent({ ...@@ -95,7 +59,8 @@ export default defineComponent({
return { return {
users, users,
deleteUser deleteUser,
tableTitles
} }
}, },
}) })
...@@ -111,27 +76,6 @@ export default defineComponent({ ...@@ -111,27 +76,6 @@ export default defineComponent({
height: 100%; height: 100%;
} }
.table-box {
border: 2px rgb(222, 222, 222) solid;
border-radius: 13px;
padding: 5px;
}
th {
padding: 10px
}
.delete-button {
border: none;
border-radius: 20px;
background: #ff8585;
color: white;
}
.delete-button:hover {
background: #ff5757;
}
.fee-box { .fee-box {
display: flex; display: flex;
} }
...@@ -142,32 +86,6 @@ export default defineComponent({ ...@@ -142,32 +86,6 @@ export default defineComponent({
border-top-left-radius: 8px; border-top-left-radius: 8px;
border-bottom-left-radius: 8px; border-bottom-left-radius: 8px;
} }
tr:nth-child(even) {
background-color: rgb(224, 224, 224);
}
td:first-child {
border-top-left-radius: 15px;
border-bottom-left-radius: 15px;
}
td:last-child {
border-top-right-radius: 15px;
border-bottom-right-radius: 15px;
}
.title-box {
display: flex;
justify-content: space-between;
margin-bottom: 40px;
}
.title {
order: 0;
font-weight: bold;
}
} }
@media (max-width: 768px) { @media (max-width: 768px) {
...@@ -178,51 +96,7 @@ export default defineComponent({ ...@@ -178,51 +96,7 @@ export default defineComponent({
min-height: 100vh; min-height: 100vh;
} }
.table-box { .danger-button {
overflow: scroll;
}
th {
padding: 10px
}
.table {
border: 2px rgb(222, 222, 222) solid;
border-radius: 13px;
padding: 5px;
white-space: nowrap;
}
td {
padding: 10px
}
.title-box {
display: flex;
justify-content: space-between;
padding: 10px;
}
.title {
order: 0;
font-weight: bold;
}
tr:nth-child(even) {
background-color: rgb(224, 224, 224);
}
td:first-child {
border-top-left-radius: 15px;
border-bottom-left-radius: 15px;
}
td:last-child {
border-top-right-radius: 15px;
border-bottom-right-radius: 15px;
}
.delete-button {
border: none; border: none;
border-radius: 20px; border-radius: 20px;
background: #ff8585; background: #ff8585;
......
<template>
<div class="body">
<div class="content">
<div class="text">
<div class="icon">
<i class="bi bi-envelope-check-fill"></i>
</div>
<h1>Email verification</h1>
<div>{{noti}} </div>
</div>
</div>
</div>
</template>
<script>
import { ref } from 'vue'
import router from '@/router'
export default {
name: 'VerifyEmail',
setup() {
const timer = ref(6); // Countdown timer
const noti = ref()
const interval = setInterval(() => {
timer.value -= 1; // Decrease countdown by 1 every second
noti.value = "You have been verified, redirect to login page in " + timer.value + " seconds";
if (timer.value === 0) {
clearInterval(interval); // Stop the interval when countdown reaches 0
router.push('/'); // Redirect after countdown reaches 0
}
}, 1000);
return {
noti,
timer
}
}
}
</script>
<style scoped>
.body {
background-color: rgb(240, 240, 240);
display: flex;
flex-direction: column;
padding: 20px;
border-radius: 3px;
height: 100vh;
}
.content {
display: flex;
flex-direction: column;
align-items: center;
height: 100%;
}
.icon {
font-size: 100px;
color: #93e4c1;
}
h1 {
font-size: 50px;
font-weight: bold;
}
.text {
margin-top: 100px;
padding: 100px;
text-align: center;
background-color: white;
}
</style>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment