- Add tailwind css for layout - remove bootstrap - Add primevue components (table) - Update to Vue 3 - Add quasar framework (use q-list) - Replace moment js (deprecated) with luxon - Update fullcalendarpull/3890/head
parent
9ad21d561b
commit
c2cc7f4548
@ -0,0 +1,16 @@ |
||||
const tailwindcss = require('tailwindcss'); |
||||
|
||||
const plugins = [ |
||||
tailwindcss, |
||||
require('autoprefixer'), |
||||
] |
||||
|
||||
if (process.env.QUASAR_RTL) { |
||||
plugins.push( |
||||
require('postcss-rtl')({}) |
||||
) |
||||
} |
||||
|
||||
module.exports = { |
||||
plugins |
||||
} |
||||
@ -1,3 +1,62 @@ |
||||
|
||||
@import '~bootstrap/scss/bootstrap'; |
||||
@import '~bootstrap-vue/src/index.scss'; |
||||
// |
||||
////import 'primevue/resources/themes/saga-blue/theme.css'; |
||||
////import 'primevue/resources/primevue.min.css'; |
||||
////@import '~bootstrap/scss/bootstrap'; |
||||
// |
||||
@import "~bootstrap/scss/functions"; |
||||
@import "~bootstrap/scss/variables"; |
||||
@import "~bootstrap/scss/mixins"; |
||||
@import "~bootstrap/scss/utilities"; |
||||
// |
||||
//// Layout & components |
||||
////@import "~bootstrap/scss/root"; |
||||
////@import "~bootstrap/scss/reboot"; |
||||
////@import "~bootstrap/scss/type"; |
||||
////@import "~bootstrap/scss/images"; |
||||
////@import "~bootstrap/scss/containers"; |
||||
////@import "~bootstrap/scss/grid"; |
||||
//@import "~bootstrap/scss/tables"; |
||||
////@import "~bootstrap/scss/forms"; |
||||
////@import "~bootstrap/scss/buttons"; |
||||
////@import "~bootstrap/scss/transitions"; |
||||
////@import "~bootstrap/scss/dropdown"; |
||||
//@import "~bootstrap/scss/button-group"; |
||||
@import "~bootstrap/scss/nav"; |
||||
////@import "~bootstrap/scss/navbar"; |
||||
//@import "~bootstrap/scss/card"; |
||||
//@import "~bootstrap/scss/accordion"; |
||||
//@import "~bootstrap/scss/breadcrumb"; |
||||
//@import "~bootstrap/scss/pagination"; |
||||
//@import "~bootstrap/scss/badge"; |
||||
//@import "~bootstrap/scss/alert"; |
||||
//@import "~bootstrap/scss/progress"; |
||||
@import "~bootstrap/scss/list-group"; |
||||
//@import "~bootstrap/scss/close"; |
||||
//@import "~bootstrap/scss/toasts"; |
||||
//@import "~bootstrap/scss/modal"; |
||||
//@import "~bootstrap/scss/tooltip"; |
||||
//@import "~bootstrap/scss/popover"; |
||||
//@import "~bootstrap/scss/carousel"; |
||||
//@import "~bootstrap/scss/spinners"; |
||||
//@import "~bootstrap/scss/offcanvas"; |
||||
// |
||||
// |
||||
////@import '~mdb-ui-kit/css/mdb.min.css'; |
||||
////@import 'node_modules/mdb-ui-kit/src/scss/mdb.core'; |
||||
////@import 'node_modules/mdb-ui-kit/src/scss/mdb.free'; |
||||
// |
||||
////[class*="q-"].row { |
||||
//// margin-left: unset; |
||||
//// margin-right: unset; |
||||
////} |
||||
// |
||||
//header .row>* { |
||||
// width: auto !important; |
||||
// --bs-gutter-x: 0rem !important; |
||||
// --bs-gutter-y: 0rem !important; |
||||
//} |
||||
// |
||||
//.q-menu .row { |
||||
// --bs-gutter-x: 0rem !important; |
||||
// --bs-gutter-y: 0rem !important; |
||||
//} |
||||
@ -1,51 +1,55 @@ |
||||
<template> |
||||
<b-modal |
||||
v-model="show" |
||||
persistent |
||||
hide-footer |
||||
> |
||||
<b-card> |
||||
<b-card-text>{{ $t('Are you sure you want to delete this item?') }}</b-card-text> |
||||
<b-button |
||||
color="error darken-1" |
||||
@click="handleDelete" |
||||
>{{ $t('Yes') }} |
||||
</b-button> |
||||
<b-button |
||||
color="secondary darken-1" |
||||
text |
||||
@click.stop="show = false" |
||||
>{{ $t('No') }} |
||||
</b-button> |
||||
</b-card> |
||||
</b-modal> |
||||
<!-- <b-modal--> |
||||
<!-- v-model="show"--> |
||||
<!-- persistent--> |
||||
<!-- hide-footer--> |
||||
<!-- >--> |
||||
<!-- <b-card>--> |
||||
<!-- <b-card-text>{{ $t('Are you sure you want to delete this item?') }}</b-card-text>--> |
||||
<!-- <b-button--> |
||||
<!-- color="error darken-1"--> |
||||
<!-- @click="handleDelete"--> |
||||
<!-- >{{ $t('Yes') }}--> |
||||
<!-- </b-button>--> |
||||
<!-- <b-button--> |
||||
<!-- color="secondary darken-1"--> |
||||
<!-- text--> |
||||
<!-- @click.stop="show = false"--> |
||||
<!-- >{{ $t('No') }}--> |
||||
<!-- </b-button>--> |
||||
<!-- </b-card>--> |
||||
<!-- </b-modal>--> |
||||
<q-dialog :value="show" persistent> |
||||
<q-card> |
||||
<q-card-section class="row items-center"> |
||||
<q-avatar icon="warning" color="primary" text-color="white" /> |
||||
<span class="q-ml-sm">{{ $t('Are you sure you want to delete this item?') }}</span> |
||||
</q-card-section> |
||||
|
||||
<q-card-actions align="right"> |
||||
<q-btn flat label="Cancel" color="primary" v-close-popup @click="handleCancel" /> |
||||
<q-btn flat label="Delete" color="primary" v-close-popup @click="handleDelete" /> |
||||
</q-card-actions> |
||||
</q-card> |
||||
</q-dialog> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
name: 'ConfirmDelete', |
||||
props: { |
||||
visible: { |
||||
show: { |
||||
type: Boolean, |
||||
required: true, |
||||
default: () => false |
||||
}, |
||||
handleDelete: { |
||||
type: Function, |
||||
required: true |
||||
} |
||||
required: true, |
||||
}, |
||||
handleCancel: { |
||||
type: Function, |
||||
required: false, |
||||
}, |
||||
}, |
||||
computed: { |
||||
show: { |
||||
get() { |
||||
return this.visible; |
||||
}, |
||||
set(value) { |
||||
if (!value) { |
||||
this.$emit('close'); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
}; |
||||
</script> |
||||
|
||||
@ -0,0 +1,27 @@ |
||||
<template> |
||||
<div class="flex h-screen bg-gray-200 font-roboto"> |
||||
<Sidebar /> |
||||
|
||||
<div class="flex-1 flex flex-col overflow-hidden"> |
||||
<Header /> |
||||
|
||||
<main class="flex-1 overflow-x-hidden overflow-y-auto bg-gray-100"> |
||||
<div class="container mx-auto px-6 py-8"> |
||||
<slot /> |
||||
</div> |
||||
</main> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<script lang="ts"> |
||||
import { defineComponent, ref } from "vue"; |
||||
import Sidebar from "./../sidebar/Sidebar.vue"; |
||||
import Header from "./../sidebar/Header.vue"; |
||||
export default defineComponent({ |
||||
components: { |
||||
Header, |
||||
Sidebar, |
||||
}, |
||||
}); |
||||
</script> |
||||
@ -0,0 +1,6 @@ |
||||
<template> |
||||
<div class="flex flex-col items-center justify-center min-h-screen p-4 space-y-4 antialiased text-gray-900 bg-white"> |
||||
<slot></slot> |
||||
</div> |
||||
</template> |
||||
|
||||
@ -1,45 +0,0 @@ |
||||
<template> |
||||
<footer class="footer mt-auto px-4"> |
||||
<b-row |
||||
align-v="center" |
||||
class="justify-content-lg-between" |
||||
> |
||||
<b-col lg="6"> |
||||
<div class="text-center text-lg-left text-muted"> |
||||
© {{ year }} |
||||
<a |
||||
href="https://www.chamilo.org" |
||||
class="font-weight-bold ml-1" |
||||
target="_blank" |
||||
> |
||||
Chamilo |
||||
</a> |
||||
</div> |
||||
</b-col> |
||||
<b-col lg="6"> |
||||
<b-nav |
||||
align="center" |
||||
class="nav-footer justify-content-lg-end" |
||||
> |
||||
<b-nav-item |
||||
href="#" |
||||
target="_blank" |
||||
> |
||||
About |
||||
</b-nav-item> |
||||
</b-nav> |
||||
</b-col> |
||||
</b-row> |
||||
</footer> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
data() { |
||||
return { |
||||
year: new Date().getFullYear() |
||||
}; |
||||
} |
||||
}; |
||||
</script> |
||||
<style></style> |
||||
@ -1,141 +0,0 @@ |
||||
<template> |
||||
<b-navbar |
||||
toggleable="sm" |
||||
type="dark" |
||||
variant="primary" |
||||
fixed="top" |
||||
> |
||||
<button |
||||
v-b-toggle.sidebar-1 |
||||
type="button" |
||||
aria-label="Toggle navigation" |
||||
class="navbar-toggler mr-3" |
||||
aria-controls="sidebar-1" |
||||
> |
||||
<span class="navbar-toggler-icon" /> |
||||
</button> |
||||
|
||||
<b-navbar-brand |
||||
href="/" |
||||
class="mr-auto mr-sm-0" |
||||
> |
||||
Chamilo |
||||
</b-navbar-brand> |
||||
|
||||
<b-collapse |
||||
id="nav-collapse" |
||||
is-nav |
||||
> |
||||
<b-navbar-nav> |
||||
<!-- <b-nav-item href="#">--> |
||||
<!-- Link--> |
||||
<!-- </b-nav-item>--> |
||||
<!-- <b-nav-item--> |
||||
<!-- href="#"--> |
||||
<!-- disabled--> |
||||
<!-- >--> |
||||
<!-- Disabled--> |
||||
<!-- </b-nav-item>--> |
||||
</b-navbar-nav> |
||||
|
||||
<!-- Right aligned nav items --> |
||||
<b-navbar-nav class="ml-auto"> |
||||
<b-navbar-nav v-if="!isAuthenticated"> |
||||
<b-nav-item :to="'/login'"> |
||||
Login |
||||
</b-nav-item> |
||||
<b-nav-item :to="'/register'"> |
||||
Register |
||||
</b-nav-item> |
||||
</b-navbar-nav> |
||||
|
||||
<!-- <b-nav-form>--> |
||||
<!-- <b-form-input--> |
||||
<!-- size="sm"--> |
||||
<!-- class="mr-sm-2"--> |
||||
<!-- placeholder="Search"--> |
||||
<!-- />--> |
||||
<!-- <b-button--> |
||||
<!-- size="sm"--> |
||||
<!-- class="my-2 my-sm-0"--> |
||||
<!-- type="submit"--> |
||||
<!-- >--> |
||||
<!-- Search--> |
||||
<!-- </b-button>--> |
||||
<!-- </b-nav-form>--> |
||||
|
||||
<!-- <b-nav-item-dropdown--> |
||||
<!-- text="Lang"--> |
||||
<!-- right--> |
||||
<!-- >--> |
||||
<!-- <b-dropdown-item href="#">--> |
||||
<!-- EN--> |
||||
<!-- </b-dropdown-item>--> |
||||
<!-- <b-dropdown-item href="#">--> |
||||
<!-- ES--> |
||||
<!-- </b-dropdown-item>--> |
||||
<!-- <b-dropdown-item href="#">--> |
||||
<!-- RU--> |
||||
<!-- </b-dropdown-item>--> |
||||
<!-- <b-dropdown-item href="#">--> |
||||
<!-- FA--> |
||||
<!-- </b-dropdown-item>--> |
||||
<!-- </b-nav-item-dropdown>--> |
||||
|
||||
<b-nav-item-dropdown |
||||
v-if="isAuthenticated" |
||||
right |
||||
no-caret |
||||
toggle-class="p-0" |
||||
> |
||||
<!-- Using 'button-content' slot --> |
||||
<template v-slot:button-content> |
||||
<b-avatar variant="light" /> |
||||
</template> |
||||
<b-dropdown-text style="width: 240px;"> |
||||
{{ currentUser.username }} |
||||
</b-dropdown-text> |
||||
<b-dropdown-divider /> |
||||
<b-dropdown-item href="/main/messages/inbox.php"> |
||||
Inbox |
||||
</b-dropdown-item> |
||||
<b-dropdown-item href="/account/home"> |
||||
Profile |
||||
</b-dropdown-item> |
||||
<b-dropdown-item href="/logout"> |
||||
Logout |
||||
</b-dropdown-item> |
||||
</b-nav-item-dropdown> |
||||
</b-navbar-nav> |
||||
</b-collapse> |
||||
</b-navbar> |
||||
</template> |
||||
<script> |
||||
import { mapGetters } from 'vuex'; |
||||
|
||||
export default { |
||||
components: {}, |
||||
props: { |
||||
type: { |
||||
type: String, |
||||
default: 'default', // default|light |
||||
description: 'Look of the dashboard navbar. Default (Green) or light (gray)' |
||||
} |
||||
}, |
||||
data() { |
||||
return { |
||||
activeNotifications: false, |
||||
showMenu: false, |
||||
searchModalVisible: false, |
||||
searchQuery: '' |
||||
}; |
||||
}, |
||||
computed: { |
||||
...mapGetters({ |
||||
'isAuthenticated': 'security/isAuthenticated', |
||||
'currentUser': 'security/getUser', |
||||
}), |
||||
}, |
||||
methods: {} |
||||
}; |
||||
</script> |
||||
@ -1,108 +0,0 @@ |
||||
<template> |
||||
<b-sidebar |
||||
id="sidebar-1" |
||||
title="Chamilo" |
||||
backdrop |
||||
> |
||||
<template |
||||
v-slot:footer |
||||
> |
||||
<b-nav |
||||
v-if="!isAuthenticated" |
||||
class="d-sm-none bg-dark text-light" |
||||
> |
||||
<b-nav-item to="/login"> |
||||
{{ $t('Login') }} |
||||
</b-nav-item> |
||||
<b-nav-item to="/register"> |
||||
{{ $t('Register') }} |
||||
</b-nav-item> |
||||
</b-nav> |
||||
|
||||
<p |
||||
v-if="isAuthenticated" |
||||
class="d-sm-none px-3 py-2 mb-0 bg-dark text-light" |
||||
> |
||||
{{ currentUser.username }} |
||||
</p> |
||||
<b-nav |
||||
v-if="isAuthenticated" |
||||
class="d-sm-none bg-dark text-light" |
||||
> |
||||
<b-nav-item href="/main/messages/inbox.php"> |
||||
{{ $t('Inbox') }} |
||||
</b-nav-item> |
||||
<b-nav-item href="/account/home"> |
||||
{{ $t('Profile') }} |
||||
</b-nav-item> |
||||
<b-nav-item href="/logout"> |
||||
{{ $t('Logout') }} |
||||
</b-nav-item> |
||||
</b-nav> |
||||
</template> |
||||
|
||||
<b-nav vertical> |
||||
<b-nav-item :to="{ name: 'Index' }"> |
||||
{{ $t('Home') }} |
||||
</b-nav-item> |
||||
</b-nav> |
||||
|
||||
<template v-if="isAuthenticated"> |
||||
<b-nav vertical> |
||||
<b-nav-item |
||||
:to="{ name: 'MyCourses' }" |
||||
> |
||||
{{ $t('Courses') }} |
||||
</b-nav-item> |
||||
<b-nav-item |
||||
:to="{ name: 'MySessions' }" |
||||
> |
||||
{{ $t('Sessions') }} |
||||
</b-nav-item> |
||||
</b-nav> |
||||
</template> |
||||
|
||||
<template v-if="isAuthenticated && isAdmin"> |
||||
<b-nav vertical> |
||||
<h4 class="pt-3 px-3 mb-0"> |
||||
{{ $t('Administration') }} |
||||
</h4> |
||||
<b-nav-item |
||||
:to="'/main/admin/user_list.php'" |
||||
> |
||||
{{ $t('Users') }} |
||||
</b-nav-item> |
||||
<b-nav-item |
||||
:to="'/main/admin/course_list.php'" |
||||
> |
||||
{{ $t('Courses') }} |
||||
</b-nav-item> |
||||
<b-nav-item |
||||
:to="'/main/session/session_list.php'" |
||||
> |
||||
{{ $t('Sessions') }} |
||||
</b-nav-item> |
||||
<b-nav-item |
||||
:to="'/main/admin/index.php'" |
||||
> |
||||
{{ $t('Administration') }} |
||||
</b-nav-item> |
||||
</b-nav> |
||||
</template> |
||||
</b-sidebar> |
||||
</template> |
||||
|
||||
<script> |
||||
import { mapGetters } from 'vuex'; |
||||
|
||||
export default { |
||||
computed: { |
||||
...mapGetters({ |
||||
'isAuthenticated': 'security/isAuthenticated', |
||||
'currentUser': 'security/getUser', |
||||
'isAdmin': 'security/isAdmin', |
||||
}), |
||||
}, |
||||
}; |
||||
|
||||
</script> |
||||
@ -0,0 +1,207 @@ |
||||
<template> |
||||
<header |
||||
class="flex justify-between items-center py-4 px-6 bg-white border-b-2 border-gray-300" |
||||
> |
||||
|
||||
<div class="flex items-center" v-if="isAuthenticated"> |
||||
<q-tabs align="center" dense inline-label no-caps> |
||||
<q-route-tab to="/" label="Home" /> |
||||
<q-route-tab to="/courses" label="My courses" /> |
||||
<q-route-tab to="/main/calendar/agenda_js.php?type=personal" label="Agenda" /> |
||||
</q-tabs> |
||||
<!-- <router-link :to="{name: 'Home'}" tag="button" class="flex items-center justify-center h-10 px-4 ml-auto text-sm font-medium rounded hover:bg-gray-300">--> |
||||
<!-- Home--> |
||||
<!-- </router-link>--> |
||||
|
||||
<!-- <router-link :to="{name: 'MyCourses'}" tag="button" class="flex items-center justify-center h-10 px-4 ml-2 text-sm font-medium bg-gray-200 rounded hover:bg-gray-300">--> |
||||
<!-- My courses--> |
||||
<!-- </router-link>--> |
||||
</div> |
||||
|
||||
<div class="flex items-center"> |
||||
<button |
||||
@click="isOpen = true" |
||||
class="text-gray-500 focus:outline-none lg:hidden" |
||||
> |
||||
<svg |
||||
class="h-6 w-6" |
||||
viewBox="0 0 24 24" |
||||
fill="none" |
||||
xmlns="http://www.w3.org/2000/svg" |
||||
> |
||||
<path |
||||
d="M4 6H20M4 12H20M4 18H11" |
||||
stroke="currentColor" |
||||
stroke-width="2" |
||||
stroke-linecap="round" |
||||
stroke-linejoin="round" |
||||
/> |
||||
</svg> |
||||
</button> |
||||
|
||||
<!-- <div class="relative mx-4 lg:mx-0">--> |
||||
<!-- <span class="absolute inset-y-0 left-0 pl-3 flex items-center">--> |
||||
<!-- <svg class="h-5 w-5 text-gray-500" viewBox="0 0 24 24" fill="none">--> |
||||
<!-- <path--> |
||||
<!-- d="M21 21L15 15M17 10C17 13.866 13.866 17 10 17C6.13401 17 3 13.866 3 10C3 6.13401 6.13401 3 10 3C13.866 3 17 6.13401 17 10Z"--> |
||||
<!-- stroke="currentColor"--> |
||||
<!-- stroke-width="2"--> |
||||
<!-- stroke-linecap="round"--> |
||||
<!-- stroke-linejoin="round"--> |
||||
<!-- />--> |
||||
<!-- </svg>--> |
||||
<!-- </span>--> |
||||
|
||||
<!-- <input--> |
||||
<!-- class="w-32 sm:w-64 rounded-md pl-10 pr-4 focus:border-indigo-600"--> |
||||
<!-- type="text"--> |
||||
<!-- placeholder="Search"--> |
||||
<!-- />--> |
||||
<!-- </div>--> |
||||
</div> |
||||
|
||||
<div class="flex items-center"> |
||||
|
||||
<button class="flex mx-4 text-gray-600 focus:outline-none"> |
||||
<svg |
||||
class="h-6 w-6" |
||||
viewBox="0 0 24 24" |
||||
fill="none" |
||||
xmlns="http://www.w3.org/2000/svg" |
||||
> |
||||
<path |
||||
d="M15 17H20L18.5951 15.5951C18.2141 15.2141 18 14.6973 18 14.1585V11C18 8.38757 16.3304 6.16509 14 5.34142V5C14 3.89543 13.1046 3 12 3C10.8954 3 10 3.89543 10 5V5.34142C7.66962 6.16509 6 8.38757 6 11V14.1585C6 14.6973 5.78595 15.2141 5.40493 15.5951L4 17H9M15 17V18C15 19.6569 13.6569 21 12 21C10.3431 21 9 19.6569 9 18V17M15 17H9" |
||||
stroke="currentColor" |
||||
stroke-width="2" |
||||
stroke-linecap="round" |
||||
stroke-linejoin="round" |
||||
/> |
||||
</svg> |
||||
</button> |
||||
|
||||
<div v-if="isAuthenticated" class="relative"> |
||||
<button |
||||
@click="dropdownOpen = !dropdownOpen" |
||||
class="relative z-10 block h-8 w-8 rounded-full overflow-hidden shadow focus:outline-none" |
||||
> |
||||
<img |
||||
class="h-full w-full object-cover" |
||||
:src="userAvatar + '?w=80&h=80&fit=crop'" |
||||
alt="Your avatar" |
||||
/> |
||||
</button> |
||||
|
||||
<div |
||||
v-show="dropdownOpen" |
||||
@click="dropdownOpen = false" |
||||
class="fixed inset-0 h-full w-full z-10" |
||||
></div> |
||||
|
||||
<div |
||||
v-show="dropdownOpen" |
||||
class="absolute right-0 mt-2 py-2 w-48 bg-white rounded-md shadow-xl z-20" |
||||
> |
||||
<!-- <a--> |
||||
<!-- href="/main/messages/inbox.php"--> |
||||
<!-- class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-200 hover:text-white"--> |
||||
<!-- >Inbox</a>--> |
||||
|
||||
<!-- <a--> |
||||
<!-- href="/account/edit"--> |
||||
<!-- class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-200 hover:text-white"--> |
||||
<!-- >Settings</a>--> |
||||
|
||||
|
||||
<!-- <a--> |
||||
<!-- href="/logout"--> |
||||
<!-- class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-200 hover:text-white"--> |
||||
<!-- >Logout</a>--> |
||||
<!-- --> |
||||
|
||||
<q-list dense> |
||||
<q-item class="GL__menu-link-signed-in"> |
||||
<q-item-section> |
||||
<div>Signed in as <strong>{{ currentUser.username }}</strong></div> |
||||
</q-item-section> |
||||
</q-item> |
||||
<!-- <q-separator />--> |
||||
<!-- <q-item clickable class="GL__menu-link-status">--> |
||||
<!-- <q-item-section>--> |
||||
<!-- <div>--> |
||||
<!-- <q-icon name="tag_faces" color="blue-9" size="18px" />--> |
||||
<!-- Set your status--> |
||||
<!-- </div>--> |
||||
<!-- </q-item-section>--> |
||||
<!-- </q-item>--> |
||||
<q-separator /> |
||||
<q-item replace :to="'/main/messages/index.php'" clickable class=""> |
||||
<q-item-section>Inbox</q-item-section> |
||||
</q-item> |
||||
<q-item href="/account/home" tag="a" class=""> |
||||
<q-item-section> |
||||
Your profile |
||||
</q-item-section> |
||||
</q-item> |
||||
<q-item href="/account/edit" tag="a" class=""> |
||||
<q-item-section>Settings</q-item-section> |
||||
</q-item> |
||||
<q-item href="/logout" tag="a" clickable class=""> |
||||
<q-item-section> |
||||
Sign out |
||||
</q-item-section> |
||||
</q-item> |
||||
</q-list> |
||||
|
||||
</div> |
||||
|
||||
</div> |
||||
|
||||
<div v-else class="relative"> |
||||
<router-link :to="'/login'" tag="button" class="flex items-center justify-center h-10 px-4 ml-2 text-sm font-medium bg-gray-200 rounded hover:bg-gray-300"> |
||||
Login |
||||
</router-link> |
||||
</div> |
||||
|
||||
</div> |
||||
</header> |
||||
</template> |
||||
|
||||
<script lang="ts"> |
||||
import { defineComponent, ref, toRefs } from "vue"; |
||||
import { useSidebar } from "../../hooks/useSidebar"; |
||||
import isEmpty from "lodash"; |
||||
import {mapGetters} from "vuex"; |
||||
|
||||
export default defineComponent({ |
||||
setup() { |
||||
const dropdownOpen = ref(false); |
||||
const { isOpen } = useSidebar(); |
||||
const userAvatar = ref(window.userAvatar); |
||||
|
||||
console.log('defineComponent window.user'); |
||||
console.log(window.user); |
||||
console.log(window.userAvatar); |
||||
if (!isEmpty(window.user)) { |
||||
console.log('is logged in as ' + window.user.username); |
||||
//this.user = window.user; |
||||
userAvatar.value = window.userAvatar; |
||||
//isAuthenticated = true; |
||||
} |
||||
|
||||
console.log(userAvatar.value); |
||||
|
||||
return { |
||||
userAvatar, |
||||
isOpen, |
||||
dropdownOpen, |
||||
}; |
||||
}, |
||||
computed: { |
||||
...mapGetters({ |
||||
'isAuthenticated': 'security/isAuthenticated', |
||||
'isAdmin': 'security/isAdmin', |
||||
'currentUser': 'security/getUser', |
||||
}), |
||||
}, |
||||
}); |
||||
</script> |
||||
@ -0,0 +1,207 @@ |
||||
<template> |
||||
<div class="flex"> |
||||
<!-- Backdrop --> |
||||
<div |
||||
:class="isOpen ? 'block' : 'hidden'" |
||||
@click="isOpen = false" |
||||
class="fixed z-20 inset-0 bg-white opacity-50 transition-opacity lg:hidden" |
||||
></div> |
||||
<!-- End Backdrop --> |
||||
|
||||
<div |
||||
:class="isOpen ? 'translate-x-0 ease-out' : '-translate-x-full ease-in'" |
||||
class="fixed z-30 inset-y-0 left-0 w-64 transition duration-300 transform bg-white overflow-y-auto lg:translate-x-0 lg:static lg:inset-0" |
||||
> |
||||
<div class="flex items-center justify-center mt-4"> |
||||
<div class="flex items-center"> |
||||
<span class="text-white text-2xl mx-2 font-semibold"> |
||||
<img style="width:200px" src="/build/css/themes/chamilo/images/header-logo.png" /> |
||||
</span> |
||||
</div> |
||||
</div> |
||||
|
||||
<nav class="mt-2"> |
||||
|
||||
|
||||
<q-list v-if="isAuthenticated" padding class="text-grey-8"> |
||||
|
||||
<q-item class="GNL__drawer-item" v-ripple v-for="link in links1" :key="link.text" :to="link.url" clickable> |
||||
<q-item-section avatar> |
||||
<!-- <q-icon :name="link.icon" />--> |
||||
<span class="w-6 h-6 stroke-current"> |
||||
<font-awesome-icon :icon="link.icon" size="lg" /> |
||||
</span> |
||||
|
||||
</q-item-section> |
||||
<q-item-section> |
||||
<q-item-label>{{ link.text }}</q-item-label> |
||||
</q-item-section> |
||||
</q-item> |
||||
|
||||
<q-separator inset class="q-my-sm" /> |
||||
<span v-if="isAdmin"> |
||||
<q-item class="GNL__drawer-item" v-ripple v-for="link in links2" :key="link.text" :to="link.url" clickable> |
||||
<q-item-section avatar> |
||||
<!-- <q-icon :name="link.icon" />--> |
||||
<span class="w-6 h-6 stroke-current"> |
||||
<font-awesome-icon :icon="link.icon" size="lg" /> |
||||
</span> |
||||
|
||||
</q-item-section> |
||||
<q-item-section> |
||||
<q-item-label>{{ link.text }}</q-item-label> |
||||
</q-item-section> |
||||
</q-item> |
||||
</span> |
||||
|
||||
<q-separator inset class="q-my-sm" /> |
||||
|
||||
<q-item class="GNL__drawer-item" v-ripple v-for="link in links3" :key="link.text" clickable> |
||||
<q-item-section> |
||||
<q-item-label>{{ link.text }} |
||||
<q-icon v-if="link.icon" :name="link.icon" /> |
||||
</q-item-label> |
||||
</q-item-section> |
||||
</q-item> |
||||
|
||||
<div class="q-mt-md"> |
||||
<div class="flex flex-center q-gutter-xs"> |
||||
<a class="GNL__drawer-footer-link" href="javascript:void(0)" aria-label="About">Chamilo</a> |
||||
</div> |
||||
</div> |
||||
</q-list> |
||||
|
||||
|
||||
<q-list v-else padding class="text-grey-8"> |
||||
<q-item class="GNL__drawer-item" v-ripple v-for="link in linksAnon" :key="link.text" :to="link.url" clickable> |
||||
<q-item-section avatar> |
||||
<q-icon :name="link.icon" /> |
||||
</q-item-section> |
||||
<q-item-section> |
||||
<q-item-label>{{ link.text }}</q-item-label> |
||||
</q-item-section> |
||||
</q-item> |
||||
</q-list> |
||||
|
||||
|
||||
<!-- <router-link--> |
||||
<!-- class="flex items-center duration-200 mt-4 py-2 px-6 border-l-4"--> |
||||
<!-- :class="[$route.name === 'Dashboard' ? activeClass : inactiveClass]"--> |
||||
<!-- to="/dashboard"--> |
||||
<!-- >--> |
||||
<!-- <svg--> |
||||
<!-- class="h-5 w-5"--> |
||||
<!-- viewBox="0 0 20 20"--> |
||||
<!-- fill="none"--> |
||||
<!-- xmlns="http://www.w3.org/2000/svg"--> |
||||
<!-- >--> |
||||
<!-- <path--> |
||||
<!-- d="M2 10C2 5.58172 5.58172 2 10 2V10H18C18 14.4183 14.4183 18 10 18C5.58172 18 2 14.4183 2 10Z"--> |
||||
<!-- fill="currentColor"--> |
||||
<!-- />--> |
||||
<!-- <path--> |
||||
<!-- d="M12 2.25195C14.8113 2.97552 17.0245 5.18877 17.748 8.00004H12V2.25195Z"--> |
||||
<!-- fill="currentColor"--> |
||||
<!-- />--> |
||||
<!-- </svg>--> |
||||
<!-- <span class="mx-4">Dashboard</span>--> |
||||
<!-- </router-link>--> |
||||
|
||||
<!-- <span v-if="!isAuthenticated">--> |
||||
<!-- <router-link--> |
||||
<!-- class="flex items-center duration-200 mt-4 py-2 px-6 border-l-4"--> |
||||
<!-- v-for="link in linksAnon" :to="link.url"--> |
||||
<!-- >--> |
||||
<!-- <span class="w-6 h-6 stroke-current">--> |
||||
<!-- <font-awesome-icon :icon="link.icon" size="lg" />--> |
||||
<!-- </span>--> |
||||
<!-- <span class="mx-4 ">--> |
||||
<!-- {{ link.text }}--> |
||||
<!-- </span>--> |
||||
<!-- </router-link>--> |
||||
<!-- </span>--> |
||||
|
||||
<!-- <span v-if="isAuthenticated">--> |
||||
<!-- <router-link--> |
||||
<!-- class="flex items-center duration-200 mt-4 py-2 px-6 border-l-4"--> |
||||
<!-- v-for="link in links1" :to="link.url"--> |
||||
<!-- >--> |
||||
<!-- <span class="w-6 h-6 stroke-current">--> |
||||
<!-- <font-awesome-icon :icon="link.icon" size="lg" />--> |
||||
<!-- </span>--> |
||||
<!-- <span class="mx-4 ">--> |
||||
<!-- {{ link.text }}--> |
||||
<!-- </span>--> |
||||
<!-- </router-link>--> |
||||
|
||||
<!-- <router-link--> |
||||
<!-- class="flex items-center duration-200 mt-4 py-2 px-6 border-l-4"--> |
||||
<!-- v-for="link in links2" :to="link.url">--> |
||||
<!-- <span class="w-6 h-6 stroke-current">--> |
||||
<!-- <font-awesome-icon :icon="link.icon" size="lg" />--> |
||||
<!-- </span>--> |
||||
|
||||
<!-- <span class="mx-4 ">--> |
||||
<!-- {{ link.text }}--> |
||||
<!-- </span>--> |
||||
<!-- </router-link>--> |
||||
<!-- </span>--> |
||||
|
||||
</nav> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<script lang="ts"> |
||||
import { defineComponent, ref, computed } from "vue"; |
||||
import { useSidebar } from "../../hooks/useSidebar"; |
||||
import {mapGetters} from "vuex"; |
||||
|
||||
export default defineComponent({ |
||||
setup() { |
||||
const { isOpen } = useSidebar(); |
||||
const activeClass = ref( |
||||
"bg-gray-600 bg-opacity-25 text-gray-100 border-gray-100" |
||||
); |
||||
const inactiveClass = ref( |
||||
"border-gray-900 text-gray-500 hover:bg-gray-600 hover:bg-opacity-25 hover:text-gray-100" |
||||
); |
||||
|
||||
const links1 = [ |
||||
// { icon: 'person', url: '/courses', text: 'My courses' }, |
||||
// { icon: 'star_border', url: '/sessions', text: 'Sessions' }, |
||||
//{ icon: 'star_border', url: '/calendar', text: 'My calendar' }, |
||||
{ icon: 'compass', url: '/catalog', text: 'Explore' }, |
||||
// { icon: 'star_border', url: '/news', text: 'News' }, |
||||
]; |
||||
|
||||
const links2 = [ |
||||
{ icon: 'users', url: '/main/admin/user_list.php', text: 'Users' }, |
||||
{ icon: 'book', url: '/main/admin/course_list.php', text: 'Courses' }, |
||||
{ icon: 'book-open', url: '/main/session/session_list.php', text: 'Sessions' }, |
||||
//{ icon: fasFlask, url: '/main/admin/index.php', text: 'Administration' }, |
||||
{ icon: 'cogs', url: '/main/admin/index.php', text: 'Administration' }, |
||||
]; |
||||
|
||||
const linksAnon = [ |
||||
{ icon: 'home', url: '/', text: 'Home' }, |
||||
]; |
||||
|
||||
return { |
||||
linksAnon, |
||||
links1, |
||||
links2, |
||||
isOpen, |
||||
activeClass, |
||||
inactiveClass, |
||||
}; |
||||
}, |
||||
computed: { |
||||
...mapGetters({ |
||||
'isAuthenticated': 'security/isAuthenticated', |
||||
'isAdmin': 'security/isAdmin', |
||||
'currentUser': 'security/getUser', |
||||
}), |
||||
}, |
||||
}); |
||||
</script> |
||||
@ -0,0 +1,11 @@ |
||||
import { reactive, toRefs } from "vue"; |
||||
|
||||
const state = reactive({ |
||||
isOpen: false, |
||||
}); |
||||
|
||||
export function useSidebar() { |
||||
return { |
||||
...toRefs(state), |
||||
}; |
||||
} |
||||
@ -0,0 +1,22 @@ |
||||
import { ref } from 'vue' |
||||
|
||||
const isSidebarOpen = ref(false) |
||||
const toggleSidebar = () => { |
||||
isSidebarOpen.value = !isSidebarOpen.value |
||||
} |
||||
|
||||
const isSettingsPanelOpen = ref(false) |
||||
|
||||
const isSearchPanelOpen = ref(false) |
||||
|
||||
const isNotificationsPanelOpen = ref(false) |
||||
|
||||
export default function useState() { |
||||
return { |
||||
isSidebarOpen, |
||||
toggleSidebar, |
||||
isSettingsPanelOpen, |
||||
isSearchPanelOpen, |
||||
isNotificationsPanelOpen, |
||||
} |
||||
} |
||||
@ -0,0 +1,13 @@ |
||||
<template> |
||||
<q-layout> |
||||
<q-tabs align="left" dense inline-label no-caps> |
||||
<q-route-tab to="/catalog/course" label="Courses" /> |
||||
<q-route-tab to="/catalog/session" label="Sessions" /> |
||||
</q-tabs> |
||||
|
||||
<!-- this is where the Pages are injected --> |
||||
<q-page-container> |
||||
<router-view></router-view> |
||||
</q-page-container> |
||||
</q-layout> |
||||
</template> |
||||
@ -0,0 +1,13 @@ |
||||
<template> |
||||
<q-layout> |
||||
<q-tabs align="left" dense inline-label no-caps> |
||||
<q-route-tab to="/courses" label="My courses" /> |
||||
<q-route-tab to="/sessions" label="My sessions" /> |
||||
</q-tabs> |
||||
|
||||
<!-- this is where the Pages are injected --> |
||||
<q-page-container> |
||||
<router-view></router-view> |
||||
</q-page-container> |
||||
</q-layout> |
||||
</template> |
||||
@ -0,0 +1,23 @@ |
||||
<template> |
||||
<div> |
||||
Welcome ! |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
name: "Index", |
||||
data() { |
||||
return { |
||||
}; |
||||
}, |
||||
computed: { |
||||
|
||||
}, |
||||
created() { |
||||
|
||||
}, |
||||
methods: { |
||||
} |
||||
} |
||||
</script> |
||||
@ -0,0 +1,19 @@ |
||||
import './styles/quasar.sass' |
||||
import '@quasar/extras/material-icons/material-icons.css' |
||||
import '@quasar/extras/fontawesome-v5/fontawesome-v5.css' |
||||
|
||||
import { Notify } from 'quasar' |
||||
|
||||
export default { |
||||
config: { |
||||
notify: { |
||||
} |
||||
}, |
||||
plugins: [ |
||||
Notify |
||||
], |
||||
extras: [ |
||||
'material-icons', |
||||
'fontawesome-v5', |
||||
] |
||||
} |
||||
@ -0,0 +1,7 @@ |
||||
|
||||
@import 'quasar.variables' |
||||
@import '~quasar/dist/quasar.sass' |
||||
|
||||
|
||||
//@import '~quasar-style' |
||||
// @import '~quasar-addon-styl' |
||||
@ -0,0 +1,15 @@ |
||||
// It's highly recommended to change the default colors |
||||
// to match your app's branding. |
||||
|
||||
$primary : #027BE3 |
||||
$secondary : #26A69A |
||||
$accent : #9C27B0 |
||||
|
||||
$dark : #1D1D1D |
||||
|
||||
$positive : #21BA45 |
||||
$negative : #C10015 |
||||
$info : #31CCEC |
||||
$warning : #F2C037 |
||||
|
||||
//@import '~quasar-variables-styl' |
||||
@ -1,9 +1,9 @@ |
||||
import moment from 'moment'; |
||||
const { DateTime } = require("luxon"); |
||||
|
||||
const formatDateTime = function(date) { |
||||
if (!date) return null; |
||||
|
||||
return moment(date).format('DD/MM/YYYY'); |
||||
return DateTime(date).format('DD/MM/YYYY'); |
||||
}; |
||||
|
||||
export { formatDateTime }; |
||||
|
||||
@ -1,24 +0,0 @@ |
||||
<template> |
||||
<v-container |
||||
class="fill-height" |
||||
fluid |
||||
/> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
name: "Home", |
||||
data() { |
||||
return { |
||||
}; |
||||
}, |
||||
computed: { |
||||
|
||||
}, |
||||
created() { |
||||
|
||||
}, |
||||
methods: { |
||||
} |
||||
} |
||||
</script> |
||||
@ -1,118 +1,115 @@ |
||||
<template> |
||||
<div class="mt-5 p-5"> |
||||
<b-container |
||||
fluid |
||||
> |
||||
<b-row> |
||||
<b-col cols="4" /> |
||||
<b-col cols="4"> |
||||
<form |
||||
@submit="onSubmit" |
||||
> |
||||
<p class="h4 text-center mb-4"> |
||||
{{ $t('Sign in') }} |
||||
</p> |
||||
<div class="grey-text"> |
||||
<b-form-input |
||||
v-model="login" |
||||
:placeholder="$t('Login')" |
||||
icon="envelope" |
||||
type="text" |
||||
required |
||||
name="login" |
||||
/> |
||||
<b-form-input |
||||
v-model="password" |
||||
:placeholder=" $t('Password') " |
||||
icon="lock" |
||||
type="password" |
||||
name="password" |
||||
required |
||||
/> |
||||
</div> |
||||
<div class=" flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8"> |
||||
<div class="max-w-md w-full space-y-8"> |
||||
<div> |
||||
<!-- <img class="mx-auto h-12 w-auto" src="https://tailwindui.com/img/logos/workflow-mark-indigo-600.svg" alt="Workflow" />--> |
||||
<h2 class="mt-6 text-center text-3xl font-extrabold text-gray-900"> |
||||
Login To Your Account |
||||
</h2> |
||||
</div> |
||||
<form class="mt-8 space-y-6" @submit.prevent="onSubmit"> |
||||
<input type="hidden" name="remember" value="true" /> |
||||
<div class="rounded-md shadow-sm -space-y-px"> |
||||
<div> |
||||
<label for="email-address" class="sr-only">Username</label> |
||||
<input id="email-address" v-model="login" name="login" type="text" autocomplete="login" required="" class="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm" |
||||
placeholder="Username" /> |
||||
</div> |
||||
<div> |
||||
<label for="password" class="sr-only">Password</label> |
||||
<input id="password" v-model="password" name="password" type="password" autocomplete="current-password" required="" class="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm" |
||||
placeholder="Password" /> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="text-center"> |
||||
<b-button |
||||
block |
||||
type="submit" |
||||
variant="primary" |
||||
> |
||||
{{ $t('Login') }} |
||||
</b-button> |
||||
</div> |
||||
<div class="flex items-center justify-between"> |
||||
<div class="flex items-center"> |
||||
<input id="remember_me" name="remember_me" type="checkbox" class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" /> |
||||
<label for="remember_me" class="ml-2 block text-sm text-gray-900"> |
||||
Remember me |
||||
</label> |
||||
</div> |
||||
|
||||
<div |
||||
v-if="isLoading" |
||||
class="row col" |
||||
> |
||||
<p><font-awesome-icon icon="spinner" /></p> |
||||
</div> |
||||
<div class="text-sm"> |
||||
<a href="/main/auth/lostPassword.php" id="forgot" class="font-medium text-blue-600 hover:text-blue-500"> |
||||
Forgot your password? |
||||
</a> |
||||
</div> |
||||
</div> |
||||
|
||||
<div |
||||
v-else-if="hasError" |
||||
class="row col" |
||||
> |
||||
<error-message :error="error" /> |
||||
</div> |
||||
<div> |
||||
<button |
||||
type="submit" |
||||
class="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"> |
||||
|
||||
<a href="/main/auth/lostPassword.php" id="forgot">Forgot password?</a> |
||||
</form> |
||||
</b-col> |
||||
<b-col cols="4" /> |
||||
</b-row> |
||||
</b-container> |
||||
<span class="absolute left-0 inset-y-0 flex items-center pl-3"> |
||||
<svg v-if="isLoading" |
||||
class="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> |
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> |
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> |
||||
</svg> |
||||
<LockClosedIcon v-if="!isLoading" class="h-5 w-5 text-blue-500 group-hover:text-blue-400" aria-hidden="true" /> |
||||
</span> |
||||
Sign in |
||||
</button> |
||||
</div> |
||||
</form> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import { mapGetters } from 'vuex'; |
||||
import ErrorMessage from "../components/ErrorMessage"; |
||||
import { mapGetters } from 'vuex'; |
||||
import ErrorMessage from "../components/ErrorMessage.vue"; |
||||
import { LockClosedIcon } from '@heroicons/vue/solid' |
||||
|
||||
export default { |
||||
name: "Login", |
||||
components: { |
||||
ErrorMessage, |
||||
}, |
||||
data() { |
||||
return { |
||||
login: "", |
||||
password: "", |
||||
}; |
||||
}, |
||||
computed: { |
||||
...mapGetters({ |
||||
'isLoading': 'security/isLoading', |
||||
'hasError': 'security/hasError', |
||||
'error': 'security/error', |
||||
}), |
||||
|
||||
export default { |
||||
name: "Login", |
||||
components: { |
||||
ErrorMessage, |
||||
LockClosedIcon |
||||
}, |
||||
data() { |
||||
return { |
||||
login: "", |
||||
password: "", |
||||
}; |
||||
}, |
||||
computed: { |
||||
...mapGetters({ |
||||
'isLoading': 'security/isLoading', |
||||
'hasError': 'security/hasError', |
||||
'error': 'security/error', |
||||
}), |
||||
}, |
||||
created() { |
||||
let redirect = this.$route.query.redirect; |
||||
if (this.$store.getters["security/isAuthenticated"]) { |
||||
if (typeof redirect !== "undefined") { |
||||
this.$router.push({path: redirect}); |
||||
} else { |
||||
this.$router.push({path: "/courses"}); |
||||
} |
||||
} |
||||
}, |
||||
methods: { |
||||
onSubmit(evt) { |
||||
evt.preventDefault() |
||||
this.performLogin(); |
||||
}, |
||||
created() { |
||||
async performLogin() { |
||||
let payload = {login: this.$data.login, password: this.$data.password}; |
||||
let redirect = this.$route.query.redirect; |
||||
if (this.$store.getters["security/isAuthenticated"]) { |
||||
await this.$store.dispatch("security/login", payload); |
||||
if (!this.$store.getters["security/hasError"]) { |
||||
if (typeof redirect !== "undefined") { |
||||
this.$router.push({path: redirect}); |
||||
} else { |
||||
this.$router.push({path: "/courses"}); |
||||
} |
||||
} |
||||
}, |
||||
methods: { |
||||
onSubmit(evt) { |
||||
evt.preventDefault() |
||||
this.performLogin(); |
||||
}, |
||||
async performLogin() { |
||||
let payload = {login: this.$data.login, password: this.$data.password}; |
||||
let redirect = this.$route.query.redirect; |
||||
await this.$store.dispatch("security/login", payload); |
||||
if (!this.$store.getters["security/hasError"]) { |
||||
if (typeof redirect !== "undefined") { |
||||
this.$router.push({path: redirect}); |
||||
} else { |
||||
this.$router.push({path: "/courses"}); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
@ -0,0 +1,274 @@ |
||||
<template> |
||||
<div class="card"> |
||||
<DataView :value="courses" :layout="layout" :paginator="true" :rows="9" :sortOrder="sortOrder" :sortField="sortField"> |
||||
<template #header> |
||||
<div class="p-grid p-nogutter"> |
||||
<div class="p-col-3" style="text-align: left"> |
||||
<Dropdown |
||||
v-model="sortKey" |
||||
:options="sortOptions" |
||||
optionLabel="label" |
||||
placeholder="Sort By Title" |
||||
@change="onSortChange($event)" |
||||
/> |
||||
</div> |
||||
<!-- <div class="p-col-3" style="text-align: left">--> |
||||
<!-- <Dropdown--> |
||||
<!-- v-model="sortKey"--> |
||||
<!-- :options="sortOptions"--> |
||||
<!-- optionLabel="label"--> |
||||
<!-- placeholder="Categories"--> |
||||
<!-- @change="onSortChange($event)"--> |
||||
<!-- />--> |
||||
<!-- </div>--> |
||||
<!-- <div class="p-col-6" style="text-align: right">--> |
||||
<!-- <DataViewLayoutOptions v-model="layout" />--> |
||||
<!-- </div>--> |
||||
</div> |
||||
</template> |
||||
|
||||
<template #list="slotProps"> |
||||
<div class="p-col-12"> |
||||
<div class="course-list-item"> |
||||
|
||||
<img src="/img/session_default.png" :alt="slotProps.data.title"/> |
||||
|
||||
<div class="course-list-detail"> |
||||
<div class="course-name">{{ slotProps.data.title }}</div> |
||||
<div class="course-description">{{ slotProps.data.description }}</div> |
||||
<!-- <Rating :modelValue="slotProps.data.rating" :readonly="true" :cancel="false"></Rating>--> |
||||
|
||||
<span v-for="category in slotProps.data.categories"> |
||||
<i class="pi pi-tag course-category-icon"></i> |
||||
<span class="course-category">{{ category.name }}</span> |
||||
</span> |
||||
</div> |
||||
<div class="course-list-action"> |
||||
<!-- <span class="course-price">${{slotProps.data.price}}</span>--> |
||||
<!-- <Button icon="pi pi-shopping-cart" label="Add to Cart" :disabled="slotProps.data.inventoryStatus === 'OUTOFSTOCK'"></Button>--> |
||||
<!-- <span :class="'course-badge status-'+slotProps.data.inventoryStatus.toLowerCase()">{{slotProps.data.inventoryStatus}}</span>--> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<template #grid="slotProps"> |
||||
<div class="p-col-12 p-md-4"> |
||||
<div class="course-grid-item card"> |
||||
<div class="course-grid-item-top"> |
||||
<div> |
||||
<i class="pi pi-tag course-category-icon"></i> |
||||
<span class="course-category">{{ slotProps.data.title }}</span> |
||||
</div> |
||||
<!-- <span :class="'course-badge status-'+slotProps.data.inventoryStatus.toLowerCase()">{{slotProps.data.inventoryStatus}}</span>--> |
||||
</div> |
||||
<div class="course-grid-item-content"> |
||||
<img src="/img/icons/64/course.png" :alt="slotProps.data.title"/> |
||||
<div class="course-name">{{ slotProps.data.title }}</div> |
||||
<div class="course-description">{{ slotProps.data.description }}</div> |
||||
<!-- <Rating :modelValue="slotProps.data.rating" :readonly="true" :cancel="false"></Rating>--> |
||||
</div> |
||||
<div class="course-grid-item-bottom"> |
||||
<!-- <span class="course-price">${{slotProps.data.price}}</span>--> |
||||
<!-- <Button icon="pi pi-shopping-cart" :disabled="slotProps.data.inventoryStatus === 'OUTOFSTOCK'"></Button>--> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
</DataView> |
||||
</div> |
||||
</template> |
||||
|
||||
<style lang="scss" scoped> |
||||
.card { |
||||
background: #ffffff; |
||||
padding: 2rem; |
||||
box-shadow: 0 2px 1px -1px rgba(0,0,0,.2), 0 1px 1px 0 rgba(0,0,0,.14), 0 1px 3px 0 rgba(0,0,0,.12); |
||||
border-radius: 4px; |
||||
margin-bottom: 2rem; |
||||
} |
||||
.p-dropdown { |
||||
width: 14rem; |
||||
font-weight: normal; |
||||
} |
||||
|
||||
.course-name { |
||||
font-size: 1.5rem; |
||||
font-weight: 700; |
||||
} |
||||
|
||||
.course-description { |
||||
margin: 0 0 1rem 0; |
||||
} |
||||
|
||||
.course-category-icon { |
||||
vertical-align: middle; |
||||
margin-right: .5rem; |
||||
} |
||||
|
||||
.course-category { |
||||
font-weight: 600; |
||||
vertical-align: middle; |
||||
} |
||||
|
||||
::v-deep(.course-list-item) { |
||||
display: flex; |
||||
align-items: center; |
||||
padding: 1rem; |
||||
width: 100%; |
||||
|
||||
img { |
||||
width: 150px; |
||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23); |
||||
margin-right: 2rem; |
||||
} |
||||
|
||||
.course-list-detail { |
||||
flex: 1 1 0; |
||||
} |
||||
|
||||
.p-rating { |
||||
margin: 0 0 .5rem 0; |
||||
} |
||||
|
||||
.course-price { |
||||
font-size: 1.5rem; |
||||
font-weight: 600; |
||||
margin-bottom: .5rem; |
||||
align-self: flex-end; |
||||
} |
||||
|
||||
.course-list-action { |
||||
display: flex; |
||||
flex-direction: column; |
||||
} |
||||
|
||||
.p-button { |
||||
margin-bottom: .5rem; |
||||
} |
||||
} |
||||
|
||||
::v-deep(.course-grid-item) { |
||||
margin: .5rem; |
||||
border: 1px solid #dee2e6; |
||||
|
||||
.course-grid-item-top, |
||||
.course-grid-item-bottom { |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: space-between; |
||||
} |
||||
|
||||
img { |
||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23); |
||||
margin: 2rem 0; |
||||
} |
||||
|
||||
.course-grid-item-content { |
||||
text-align: center; |
||||
} |
||||
|
||||
.course-price { |
||||
font-size: 1.5rem; |
||||
font-weight: 600; |
||||
} |
||||
} |
||||
|
||||
@media screen and (max-width: 576px) { |
||||
.course-list-item { |
||||
flex-direction: column; |
||||
align-items: center; |
||||
|
||||
img { |
||||
margin: 2rem 0; |
||||
} |
||||
|
||||
.course-list-detail { |
||||
text-align: center; |
||||
} |
||||
|
||||
.course-price { |
||||
align-self: center; |
||||
} |
||||
|
||||
.course-list-action { |
||||
display: flex; |
||||
flex-direction: column; |
||||
} |
||||
|
||||
.course-list-action { |
||||
margin-top: 2rem; |
||||
flex-direction: row; |
||||
justify-content: space-between; |
||||
align-items: center; |
||||
width: 100%; |
||||
} |
||||
} |
||||
} |
||||
</style> |
||||
<script> |
||||
|
||||
import {ENTRYPOINT} from '../../config/entrypoint'; |
||||
import axios from "axios"; |
||||
import Dropdown from "primevue/dropdown"; |
||||
import DataView from 'primevue/dataview'; |
||||
import DataViewLayoutOptions from 'primevue/dataviewlayoutoptions'; |
||||
|
||||
export default { |
||||
name: 'Catalog', |
||||
components: { |
||||
DataView, |
||||
Dropdown, |
||||
DataViewLayoutOptions |
||||
}, |
||||
data() { |
||||
return { |
||||
status: '', |
||||
courses: [], |
||||
layout: 'list', |
||||
sortKey: null, |
||||
sortOrder: null, |
||||
sortField: null, |
||||
sortOptions: [ |
||||
{label: 'A-z', value: 'title'}, |
||||
{label: 'Z-a', value: '!title'}, |
||||
] |
||||
}; |
||||
}, |
||||
created: function () { |
||||
this.load(); |
||||
}, |
||||
mounted: function () { |
||||
|
||||
}, |
||||
methods: { |
||||
load: function () { |
||||
//this.status = 'Loading'; |
||||
//let user = this.$store.getters['security/getUser']; |
||||
axios.get(ENTRYPOINT + 'courses.json').then(response => { |
||||
this.status = ''; |
||||
if (Array.isArray(response.data)) { |
||||
this.courses = response.data; |
||||
} |
||||
}).catch(function (error) { |
||||
console.log(error); |
||||
}); |
||||
}, |
||||
onSortChange(event) { |
||||
const value = event.value.value; |
||||
const sortValue = event.value; |
||||
|
||||
if (value.indexOf('!') === 0) { |
||||
this.sortOrder = -1; |
||||
this.sortField = value.substring(1, value.length); |
||||
this.sortKey = sortValue; |
||||
} |
||||
else { |
||||
this.sortOrder = 1; |
||||
this.sortField = value; |
||||
this.sortKey = sortValue; |
||||
} |
||||
} |
||||
} |
||||
}; |
||||
</script> |
||||
@ -0,0 +1,72 @@ |
||||
<template> |
||||
<div class="card"> |
||||
Session catalog todo |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
|
||||
import {ENTRYPOINT} from '../../config/entrypoint'; |
||||
import axios from "axios"; |
||||
import Dropdown from "primevue/dropdown"; |
||||
import DataView from 'primevue/dataview'; |
||||
import DataViewLayoutOptions from 'primevue/dataviewlayoutoptions'; |
||||
|
||||
export default { |
||||
name: 'Catalog', |
||||
components: { |
||||
DataView, |
||||
Dropdown, |
||||
DataViewLayoutOptions |
||||
}, |
||||
data() { |
||||
return { |
||||
status: '', |
||||
courses: [], |
||||
layout: 'list', |
||||
sortKey: null, |
||||
sortOrder: null, |
||||
sortField: null, |
||||
sortOptions: [ |
||||
{label: 'A-z', value: 'title'}, |
||||
{label: 'Z-a', value: '!title'}, |
||||
] |
||||
}; |
||||
}, |
||||
created: function () { |
||||
this.load(); |
||||
}, |
||||
mounted: function () { |
||||
|
||||
}, |
||||
methods: { |
||||
load: function () { |
||||
//this.status = 'Loading'; |
||||
//let user = this.$store.getters['security/getUser']; |
||||
axios.get(ENTRYPOINT + 'courses.json').then(response => { |
||||
this.status = ''; |
||||
if (Array.isArray(response.data)) { |
||||
this.courses = response.data; |
||||
} |
||||
}).catch(function (error) { |
||||
console.log(error); |
||||
}); |
||||
}, |
||||
onSortChange(event) { |
||||
const value = event.value.value; |
||||
const sortValue = event.value; |
||||
|
||||
if (value.indexOf('!') === 0) { |
||||
this.sortOrder = -1; |
||||
this.sortField = value.substring(1, value.length); |
||||
this.sortKey = sortValue; |
||||
} |
||||
else { |
||||
this.sortOrder = 1; |
||||
this.sortField = value; |
||||
this.sortKey = sortValue; |
||||
} |
||||
} |
||||
} |
||||
}; |
||||
</script> |
||||
@ -0,0 +1,75 @@ |
||||
<template> |
||||
<div class="grid gap-4"> |
||||
<q-card-section> |
||||
<div class="text-h6">{{ course.title }}</div> |
||||
<div class="text-subtitle2">{{ course.description }}</div> |
||||
</q-card-section> |
||||
|
||||
<div v-for="categories in tools" class="grid gap-4 grid-cols-2 md:grid-cols-4 lg:grid-cols-6"> |
||||
<div v-for="tool in categories" class="bg-gray-100 rounded-xl p-4 shadow-md"> |
||||
<div class="flex flex-col flex-center"> |
||||
<q-avatar rounded> |
||||
<img |
||||
:alt="tool.name" |
||||
:src="'/img/tools/' + tool.name + '.png'" |
||||
/> |
||||
</q-avatar> |
||||
<q-item-section> |
||||
<div class="row no-wrap items-center"> |
||||
<a |
||||
:href="goToCourse(course, tool)" |
||||
> |
||||
{{ tool.nameToTranslate }} |
||||
</a> |
||||
</div> |
||||
</q-item-section> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import Loading from '../../components/Loading.vue'; |
||||
import Toolbar from '../../components/Toolbar.vue'; |
||||
import isEmpty from 'lodash/isEmpty'; |
||||
import { useRoute } from 'vue-router' |
||||
import axios from "axios"; |
||||
import { ENTRYPOINT } from '../../config/entrypoint'; |
||||
import { reactive, toRefs} from 'vue' |
||||
|
||||
// @todo use suspense |
||||
// @ |
||||
|
||||
export default { |
||||
name: 'Home', |
||||
servicePrefix: 'Courses', |
||||
components: { |
||||
Loading, |
||||
Toolbar |
||||
}, |
||||
setup() { |
||||
const state = reactive({course: [], tools: [], shortcuts:[]}); |
||||
const route = useRoute() |
||||
let id = route.params.id; |
||||
|
||||
axios.get(ENTRYPOINT + '../course/' + id + '/home.json').then(response => { |
||||
state.course = response.data.course; |
||||
state.tools = response.data.tools; |
||||
state.shortcuts = response.data.shortcuts; |
||||
}).catch(function (error) { |
||||
console.log(error); |
||||
}); |
||||
|
||||
return toRefs(state); |
||||
}, |
||||
methods: { |
||||
goToCourse(course, tool) { |
||||
let sessionId = this.$route.query.sid ?? 0; |
||||
let url = '/course/' + course.id + '/tool/' + tool.name + '?sid=' + sessionId; |
||||
|
||||
return url; |
||||
} |
||||
} |
||||
}; |
||||
</script> |
||||
@ -0,0 +1,47 @@ |
||||
<template> |
||||
<div class="card"> |
||||
<h6>Hello {{ currentUser.username }}</h6> |
||||
<q-tabs align="left" dense inline-label no-caps> |
||||
<q-route-tab to="/courses" label="Inbox" /> |
||||
<q-route-tab to="/courses" label="Posts" /> |
||||
<q-route-tab to="/courses" label="Friends" /> |
||||
<q-route-tab to="/" label="Posts" /> |
||||
<q-route-tab to="/sessions" label="My files" /> |
||||
</q-tabs> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import { useRoute } from 'vue-router' |
||||
import axios from "axios"; |
||||
import { ENTRYPOINT } from '../../../config/entrypoint'; |
||||
import { reactive, toRefs} from 'vue' |
||||
import {mapGetters} from "vuex"; |
||||
|
||||
export default { |
||||
name: 'Home', |
||||
components: { |
||||
}, |
||||
setup() { |
||||
const state = reactive({user: []}); |
||||
const route = useRoute() |
||||
//let id = route.params.id; |
||||
|
||||
// axios.get(ENTRYPOINT + '../user/' + id + '.json').then(response => { |
||||
// state.user = response.data.user; |
||||
// }).catch(function (error) { |
||||
// console.log(error); |
||||
// }); |
||||
|
||||
return toRefs(state); |
||||
}, |
||||
computed: { |
||||
...mapGetters({ |
||||
'isAuthenticated': 'security/isAuthenticated', |
||||
'currentUser': 'security/getUser', |
||||
}), |
||||
}, |
||||
methods: { |
||||
}, |
||||
}; |
||||
</script> |
||||
@ -0,0 +1,20 @@ |
||||
module.exports = { |
||||
purge: [], |
||||
darkMode: false, // or 'media' or 'class'
|
||||
theme: { |
||||
extend: { |
||||
fontFamily: { |
||||
sans: ['Helvetica Neue', 'Helvetica', 'Arial', 'sans-serif'] |
||||
} |
||||
}, |
||||
}, |
||||
variants: { |
||||
extend: { |
||||
opacity: ['disabled'], |
||||
}, |
||||
}, |
||||
plugins: [ |
||||
require('@tailwindcss/forms'), |
||||
require('@tailwindcss/ui'), |
||||
], |
||||
} |
||||
@ -0,0 +1,10 @@ |
||||
{ |
||||
"compilerOptions": { |
||||
"target": "es5", |
||||
"module": "esnext", |
||||
// this enables stricter inference for data properties on `this` |
||||
"strict": true, |
||||
"jsx": "preserve", |
||||
"moduleResolution": "node" |
||||
} |
||||
} |
||||
Loading…
Reference in new issue