feat(app): tests

This commit is contained in:
vbuglov 2024-04-01 09:37:52 +03:00
parent eefc7ba4ac
commit 2b37e59afc
46 changed files with 1101 additions and 693 deletions

View File

@ -26,6 +26,7 @@
<link rel="stylesheet" href="/css/components/media.css">
<link rel="stylesheet" href="/css/components/menu.css">
<link rel="stylesheet" href="/css/_flatpickr.css">
<link rel="stylesheet" href="/css/animation.css">
<title>LiveMonitor</title>
</head>
<body>

View File

@ -0,0 +1,92 @@
/* Определение анимации */
@keyframes slide-left {
from {
transform: translateX(-100vw); /* Старт с -100% по оси X, чтобы меню было скрыто */
opacity: 0; /* Начальная прозрачность для плавного появления */
}
to {
transform: translateX(0vw); /* Конечное положение: на месте */
opacity: 1; /* Полная видимость */
}
}
@keyframes appear-opacity {
from {
opacity: 0; /* Начальная прозрачность для плавного появления */
}
to {
opacity: 1; /* Полная видимость */
}
}
.appear-opacity-100 {
animation: appear-opacity 0.1s ease-out forwards;
}
.appear-opacity-200 {
animation: appear-opacity 0.2s ease-out forwards;
}
.appear-opacity-300 {
animation: appear-opacity 0.3s ease-out forwards;
}
.appear-opacity-400 {
animation: appear-opacity 0.4s ease-out forwards;
}
.appear-opacity-500 {
animation: appear-opacity 0.5s ease-out forwards;
}
.appear-opacity-600 {
animation: appear-opacity 0.6s ease-out forwards;
}
.appear-opacity-700 {
animation: appear-opacity 0.7s ease-out forwards;
}
.appear-opacity-800 {
animation: appear-opacity 0.8s ease-out forwards;
}
.appear-opacity-900 {
animation: appear-opacity 0.9s ease-out forwards;
}
.slide-left-100 {
animation: slide-left 0.1s ease-out forwards;
}
.slide-left-200 {
animation: slide-left 0.2s ease-out forwards;
}
.slide-left-300 {
animation: slide-left 0.3s ease-out forwards;
}
.slide-left-400 {
animation: slide-left 0.4s ease-out forwards;
}
.slide-left-500 {
animation: slide-left 0.5s ease-out forwards;
}
.slide-left-600 {
animation: slide-left 0.6s ease-out forwards;
}
.slide-left-700 {
animation: slide-left 0.7s ease-out forwards;
}
.slide-left-800 {
animation: slide-left 0.8s ease-out forwards;
}
.slide-left-900 {
animation: slide-left 0.9s ease-out forwards;
}

View File

@ -1,11 +1,12 @@
<script>
import MenuList from './MenuList.vue'
import MobileMenu from './MobileMenu.vue'
import MenuList from './MenuList/MenuList.vue'
import MobileMenu from './AppContainerMobileMenu.vue'
import LeftPanel from './AppContainerLeftPanel.vue'
import Breadcrumbs from './AppContainerBreadcrumbs.vue'
import RightPanel from './RightPanel.vue'
import RightPanel from './AppContainerRightPanel.vue'
import AppContainerHeader from './AppContainerHeader.vue'
import {mapGetters} from 'vuex'
import ServiceOfLayout from '@services/ServiceOfLayout'
export default {
name: 'AppContainer',
@ -18,41 +19,91 @@ export default {
AppContainerHeader
},
data() {
return {
serviceOfLayout: null,
isOpenedMobileMenu: false,
routeName: ""
}
},
computed: {
...mapGetters('layout', ["show_menu", 'is_enabled_menu']),
...mapGetters('auth', ['menuList']),
...mapGetters('layout', ["isShowMenu", 'isEnabledMenu']),
...mapGetters('auth', ['menuList', 'currentUser']),
menuListWithIndex() {
return this.menuList.map((el, index) => ({...el, index}))
}
},
watch:{
$route (to){
this.routeName = to.name;
this.closeMobileMenu()
}
},
mounted() {
const store = this.$store
this.serviceOfLayout = new ServiceOfLayout(store)
},
methods: {
closeMobileMenu() {
this.isOpenedMobileMenu = false
},
openMobileMenu() {
this.isOpenedMobileMenu = true
},
setIsMobileMenuOpened(isOpened) {
if (this.serviceOfLayout) {
this.serviceOfLayout.setIsMobileMenuOpened(isOpened)
}
},
toggleMenu() {
if (this.serviceOfLayout) {
this.serviceOfLayout.toggleMenu()
}
},
toggleMobileMenu() {
if (this.isOpenedMobileMenu) {
this.closeMobileMenu()
} else {
this.openMobileMenu()
}
}
}
}
</script>
<template>
<div class="main w-full">
<div
v-if="serviceOfLayout"
class="main w-full"
>
<!-- BEGIN: TOP BAR -->
<div
id="menu"
class="top-bar-boxed w-full h-[59px] mt-[4px] z-[51] relative justify-center border-b border-white/[0.2] -mx-3 px-3 sm:pl-8 md:pt-0 mb-[4px]"
>
<MobileMenu
v-if="false"
current_menu_item=""
current_user=""
:menu_list="menuList"
v-if="isOpenedMobileMenu"
:menuListWithIndex="menuListWithIndex"
currentMenuItem="routeName"
:currentUser="currentUser"
setIsMobileMenuOpened="setIsMobileMenuOpened"
:toggleMobileMenu="toggleMobileMenu"
/>
<AppContainerHeader>
<div class="flex justify-between items-center">
<LeftPanel />
<LeftPanel
:toggleMobileMenu="toggleMobileMenu"
/>
<Breadcrumbs
v-if="false"
:breadcrumbs="[]"
/>
<RightPanel
:current_user="''"
:page_title="''"
:pageTitle="''"
:toggleMenu="toggleMenu"
:notifications="[{from: 'Бот из LM', datetime: '10:00', message: 'Проведен редизайн приложения', readed: false}]"
:currentUser="currentUser"
/>
</div>
<div class="sm:hidden flex items-center justify-between" />
@ -61,9 +112,11 @@ export default {
<!-- END: TOP BAR -->
<div class="wrapper">
<div class="wrapper-box">
<div v-if="show_menu && is_enabled_menu">
<div v-if="isShowMenu && isEnabledMenu">
<MenuList
:menu_list="menuList"
:isShowMenu="isShowMenu"
:menuListWithIndex="menuListWithIndex"
:routeName="routeName"
/>
</div>
<div class="content">

View File

@ -9,8 +9,6 @@ export default {
default: null
},
},
data() {
},
computed: {
},
methods: {

View File

@ -1,8 +1,18 @@
<script>
import { h } from 'vue'
import {createIcons, icons} from "lucide";
export default {
name: 'AppContainerLeftPanel',
components: {},
components: {
MobileMenuButton: h('div')
},
props: {
toggleMobileMenu: {
type: Function,
default: () => {}
}
},
data() {
return {
mobile_menu_opened: false
@ -10,25 +20,34 @@ export default {
},
computed: {
},
mounted() {
this.icons()
},
updated() {
this.icons()
},
methods: {
}
icons() {
createIcons({ icons, nameAttr: "icon" })
}
},
}
</script>
<template>
<div class="flex items-center border-white/[0.2] border-r">
<div
class="flex p-4 cursor-pointer flex md:hidden"
@click="mobile_menu_opened = !mobile_menu_opened"
<mobile-menu-button
class="flex p-4 cursor-pointer md:hidden"
@click="toggleMobileMenu()"
>
<i
icon="bar-chart-2"
class="w-6 h-6 text-white transform rotate-90"
/>
</div>
</mobile-menu-button>
<router-link
class="items-center relative -intro-x flex cursor-pointer p-4 hidden md:flex"
class="hidden md:flex items-center relative -intro-x cursor-pointer p-4 "
to="/?page=machines&mode=cards"
>
<div

View File

@ -0,0 +1,60 @@
<script>
import MenuListItem from './MenuList/MenuListItem.vue'
export default {
name: 'AppContainerMobileMenu',
components: {
MenuListItem
},
props: {
currentMenuItem: {
type: String,
default: ""
},
menuListWithIndex: {
type: Array,
default: () => []
},
toggleMobileMenu: {
type: Function,
required: true,
},
},
data() {
return {}
},
methods: {
randomkey() {
return Math.random().toString(36).slice(4)
},
}
}
</script>
<template>
<div class="mobile-menu--active appear-opacity-200 mobile-menu md:hidden">
<span
class="absolute top-[30px] right-0 z-40"
@click="toggleMobileMenu()"
>
<i
icon="x-circle"
class="w-8 h-8 text-white transform -rotate-90"
/>
</span>
<div class="scrollable slide-left-400">
<ul class=" scrollable__content py-2">
<MenuListItem
v-for="{index, ...menuItem} in menuListWithIndex"
:key="randomkey(index)"
baseClass="menu"
:type="menuItem.type"
:menuItem="menuItem"
:currentMenuItem="currentMenuItem"
/>
</ul>
</div>
</div>
</template>

View File

@ -1,24 +1,30 @@
<!-- eslint-disable vue/prop-name-casing -->
<script>
import {mapGetters, mapMutations} from 'vuex'
import {mapGetters} from 'vuex'
import { createIcons, icons } from "lucide";
export default {
name: 'RightPanel',
components: {},
props: {
// eslint-disable-next-line vue/prop-name-casing
page_title: {
pageTitle: {
type: String,
default: ""
},
toggleMenu: {
type: Function,
default: () => {}
},
notifications: {
type: Array,
default: () => []
},
currentUser: {
type: Object,
required: true
}
},
data() {
return {
}
},
computed: {
...mapGetters('auth', ['current_user'])
return { }
},
mounted() {
this.icons()
@ -27,11 +33,12 @@ export default {
this.icons()
},
methods: {
...mapMutations('layout', ["toggle_menu"]),
icons() {
createIcons({ icons, "stroke-width": 1.5, nameAttr: "icon" })
createIcons({
icons, "stroke-width": 1.5, nameAttr: "icon"
})
},
rkey() {
randomkey() {
return Math.random().toString(36).slice(4)
},
closeDropDown() {
@ -46,9 +53,9 @@ export default {
<template>
<div class="flex items-center justify-end grow gapy-2 px-4 border-hide-600">
<div class="flex items-center justify-end grow px-4 border-hide-600">
<div class="">
{{ page_title }}
{{ pageTitle }}
</div>
<div class="flex">
<div class="relative">
@ -66,7 +73,7 @@ export default {
/>
<div
style="font-size: 7px; font-weight: 900; top: 10px; left: 30px"
class="h-[13px] w-[13px] bg-white rounded-full mr-8 transition absolute flex itens-center justify-center"
class="h-[13px] w-[13px] bg-white rounded-full mr-8 transition absolute flex items-center justify-center"
>
<span
style="line-height: 13px; font-weight: 900; font-size: 7px"
@ -81,14 +88,17 @@ export default {
</div>
<div
v-for="{from, datetime, message, readed} in notifications"
:key="rkey(message)"
:key="randomkey(message)"
class="cursor-pointer relative flex items-center "
>
<div :class="{'bg-gray-400 w-3 h-3 absolute right-0 bottom-0 rounded-full border-2 border-white dark:border-darkmode-600|, else: ~s|bg-success w-3 h-3 absolute right-0 bottom-0 rounded-full border-2 border-white dark:border-darkmode-600': readed}" />
<div
:class="{'bg-gray-400 w-3 h-3 absolute right-0 bottom-0 rounded-full border-2 border-white dark:border-darkmode-600': readed,
'bg-success w-3 h-3 absolute right-0 bottom-0 rounded-full border-2 border-white dark:border-darkmode-600': !readed}"
/>
<div class="ml-2 overflow-hidden">
<div class="flex items-center">
<a
href="javascript:;"
href="javascript:"
class="font-medium truncate mr-5"
>{{ from }}</a>
<div class="text-xs text-slate-400 ml-auto whitespace-nowrap">
@ -120,7 +130,7 @@ export default {
</div>
<div
class="cursor-pointer hidden md:flex border-white/[0.2] border-r p-4"
@click="toggle_menu"
@click="toggleMenu"
>
<i
id="menu_library"
@ -146,13 +156,13 @@ export default {
<ul class="dropdown-content before:block before:absolute before:bg-black before:inset-0 before:rounded-md before:z-[-1]">
<li class="p-2">
<div class="font-bold">
{{ current_user?.first_name || "" }} {{ current_user?.last_name || "" }}
{{ currentUser?.first_name || "" }} {{ currentUser?.last_name || "" }}
</div>
<div
class="text-xs text-black/60 dark:text-slate-500"
style="margin-top: -0.25rem;"
>
{{ current_user?.position || "позиция не указана" }}
{{ currentUser?.position || "позиция не указана" }}
</div>
</li>
<li>

View File

@ -1,119 +0,0 @@
<!-- eslint-disable vue/prop-name-casing -->
<script>
import { cond, equals, T, always } from 'ramda'
import { createIcons, icons } from "lucide";
import MenuItemGroup from './MenuItemGroup.vue'
export default {
name: 'MenuItem',
components: {MenuItemGroup},
props: {
type: {
type: String,
default: null
},
menu_item: {
type: Object,
default: () => ({
link: "/"
})
},
base_class: {
type: String,
default: ""
},
routeName: {
type: String,
default: ""
}
},
data() {
},
computed: {
_base_class() {
return `${this.base_class}`
},
activeClass () {
return 'side-menu side-menu--active'
},
menuRouterName() {
return this.menu_item.router || ""
},
currentRouterName() {
return this.$router.currentRoute._value.name || ""
},
linkClass() {
const menuRouterName = this.menu_item.router || ""
if (menuRouterName === this.routeName)
return `${this._base_class} ${this._base_class}--active`
return this._base_class
},
_type() {
return cond([
[equals("simple_link"), (x) => x],
[equals("line"), (x) => x],
[equals("group"), (x) => x],
[T, () => "other"],
])(this.type)
}
},
mounted() {
this.icons()
},
updated() {
console.log(123)
this.icons()
},
methods: {
icons() {
createIcons({ icons, "stroke-width": 1.5, nameAttr: "icon" })
}
}
}
</script>
<template>
<li v-if="_type == 'simple_link'">
<router-link
phx-hook="MenuLink"
:class="linkClass"
:to="menu_item.link"
>
<div :class="`${_base_class}__icon`">
<i :icon="menu_item?.icon" />
</div>
<div :class="`${_base_class}__title`">
{{ menu_item.show }}
</div>
</router-link>
</li>
<li v-if="_type == 'line'">
<hr class="m-4">
</li>
<MenuItemGroup
v-if="_type == 'group'"
:type="_type"
:base_class="base_class"
:menu_item="menu_item"
/>
<div v-if="_type == 'other'">
""
</div>
</template>
<!-- <router-link
v-if="type === 'link'"
class="flex p-2 rounded-md hover:bg-gray-100 flex items-center"
:class="{'bg-gray-200': pageName === name}"
:to="link"
>
<i :class="`ri-${icon} text-slate-600 mr-2 `" />
<span
v-if="isOpenMenu"
class="text-slate-700"
>{{ title }}</span>
</router-link> -->

View File

@ -1,93 +0,0 @@
<!-- eslint-disable vue/prop-name-casing -->
<script>
import { cond, equals, T } from 'ramda'
import { createIcons, icons } from "lucide";
export default {
name: 'MenuItemCopy',
components: {},
props: {
type: {
type: String,
default: null
},
menu_item: {
type: Object,
default: () => ({
link: "/"
})
},
base_class: {
type: String,
default: ""
}
},
data() {
},
computed: {
_base_class() {
return `${this.base_class}`
},
activeClass () {
return 'side-menu side-menu--active'
},
menuRouterName() {
return this.menu_item.router || ""
},
currentRouterName() {
return this.$router.currentRoute._value.name || ""
},
linkClass() {
const menuRouterName = this.menu_item.router || ""
const currentRouterName = this.$router.currentRoute._value.name || Math.random()
if (menuRouterName === currentRouterName)
return `${this._base_class} ${this._base_class}--active`
return this._base_class
},
_type() {
return cond([
[equals("simple_link"), (x) => x],
[equals("line"), (x) => x],
[equals("group"), (x) => x],
[T, () => "other"],
])(this.type)
}
},
mounted() {
this.icons()
},
updated() {
this.icons()
},
methods: {
icons() {
createIcons({ icons, "stroke-width": 1.5, nameAttr: "icon" })
}
}
}
</script>
<template>
<li v-if="_type == 'simple_link'">
<router-link
phx-hook="MenuLink"
:class="linkClass"
:to="menu_item.link"
>
<div :class="`${_base_class}__icon`">
<i :icon="menu_item?.icon" />
</div>
<div :class="`${_base_class}__title`">
{{ menu_item.show }}
</div>
</router-link>
</li>
<li v-if="_type == 'line'">
<hr class="m-4">
</li>
<div v-if="_type == 'other'">
""
</div>
</template>

View File

@ -1,80 +0,0 @@
<!-- eslint-disable vue/prop-name-casing -->
<script>
import MenuItem from './MenuItemCopy.vue'
export default {
name: 'MenuItemGroup',
components: {
MenuItem
},
props: {
type: {
type: String,
default: null
},
base_class: {
type: String,
default: null
},
menu_item: {
type: Object,
default: () => ({})
},
},
data() {
return {
open: false
}
},
computed: {
containerClass() {
const menuRouterName = this.menu_item.router || ""
const currentRouterName = this.$router.currentRoute._value.name || Math.random()
if (menuRouterName === currentRouterName) return `${this.base_class} ${this.base_class}--active`
return `${this.base_class}`
},
itemClass() {
if (this.open) return `${this.base_class}__sub-icon transform rotate-180`
return `${this.base_class}__sub-icon`
},
listClass() {
if (this.open) return `${this.base_class}__sub-open`
return ""
},
menu_list_with_index() {
return this.menu_item.children.map((el, index) => ({...el, index}))
}
},
methods: {
toggle() {
this.open = !this.open
}
}
}
</script>
<template>
<li>
<span
:class="containerClass"
@click="toggle"
>
<div :class="`${base_class}__icon`"> <i :icon="menu_item.icon" /> </div>
<div :class="`${base_class}__title`">
{{ menu_item.show }}
<div :class="itemClass"> <i icon="chevron-down" /> </div>
</div>
</span>
<ul :class="listClass">
<MenuItem
v-for="item in menu_item.children"
:key="JSON.stringify(item)"
:base_class="base_class"
:type="item.type"
:menu_item="item"
/>
</ul>
</li>
</template>

View File

@ -1,63 +0,0 @@
<!-- eslint-disable vue/prop-name-casing -->
<script>
import {mapGetters} from 'vuex'
import MenuItem from './MenuItem.vue'
export default {
name: 'MenuList',
components: {MenuItem},
props: {
current_menu_item: {
type: String,
default: ""
},
menu_list: {
type: Array,
default: () => []
}
},
data() {
return {
routeName: ""
}
},
computed: {
...mapGetters('', ['mobile_menu_opened', 'show_menu']),
menu_list_with_index() {
return this.menu_list.map((el, index) => ({...el, index}))
}
},
watch:{
$route (to){
this.routeName = to.name;
}
},
methods: {
rkey() {
return Math.random().toString(36).slice(4)
}
}
}
</script>
<template>
<nav
x-show="show_menu"
class="side-nav"
>
<ul>
<MenuItem
v-for="{index, ...menu_item} in menu_list_with_index"
:id="`main_menu_item_component_${index}`"
:key="rkey(index)"
base_class="side-menu"
:type="menu_item.type"
:menu_item="menu_item"
:current_menu_item="current_menu_item"
:routeName="routeName"
/>
</ul>
</nav>
</template>

View File

@ -0,0 +1,54 @@
<!-- eslint-disable vue/prop-name-casing -->
<script>
import MenuListItem from './MenuListItem.vue'
export default {
name: 'MenuList',
components: {MenuListItem},
props: {
menuListWithIndex: {
type: [{
type: String,
default: null
}],
default: () => []
},
isShowMenu: {
type: Boolean,
default: false
},
routeName: {
type: String,
required: true
}
},
data() {
return {
}
},
methods: {
randomkey() {
return Math.random().toString(36).slice(4)
}
}
}
</script>
<template>
<nav class="side-nav">
<ul>
<MenuListItem
v-for="{index, ...menuItem} in menuListWithIndex"
:id="`main_menu_item_component_${index}`"
:key="randomkey(index)"
baseClass="side-menu"
:type="menuItem.type"
:menuItem="menuItem"
:routeName="routeName"
/>
</ul>
</nav>
</template>

View File

@ -0,0 +1,31 @@
<script>
import MenuListItemImplementation from './MenuListItemImplementation.vue'
import MenuListItemGroup from "@frames/AppContainer/MenuList/MenuListItemGroup.vue";
export default {
name: 'MenuListItem',
components: {MenuListItemGroup, MenuListItemImplementation},
//eslint-disable-next-line
props: ['type', 'menuItem', 'baseClass', 'routeName'],
}
</script>
<template>
<MenuListItemImplementation
:type="type"
:menuItem="menuItem"
:baseClass="baseClass"
:routeName="routeName"
>
<MenuListItemGroup
v-if="type === 'group'"
:type="type"
:baseClass="baseClass"
:menuItem="menuItem"
:routeName="routeName"
/>
</MenuListItemImplementation>
</template>

View File

@ -0,0 +1,22 @@
<script>
import MenuListItemImplementation from './MenuListItemImplementation.vue'
export default {
name: 'MenuListItemCopy',
components: {MenuListItemImplementation},
//eslint-disable-next-line
props: ['type', 'menuItem', 'baseClass', 'routeName'],
}
</script>
<template>
<MenuListItemImplementation
:type="type"
:menuItem="menuItem"
:baseClass="baseClass"
:routeName="routeName"
/>
</template>

View File

@ -0,0 +1,85 @@
<!-- eslint-disable vue/prop-name-casing -->
<script>
import MenuItem from './MenuListItemCopy.vue'
export default {
name: 'MenuItemGroup',
components: {
MenuItem
},
props: {
type: {
type: String,
default: null
},
baseClass: {
type: String,
default: null
},
menuItem: {
type: {
show: String,
children: Array,
router: String
},
default: () => ({})
},
routeName: {
type: String,
default: ""
}
},
data() {
return {
open: false
}
},
computed: {
containerClass() {
const routeName = this.routeName
const menuRouterName = this.menuItem.router || ""
if (menuRouterName === routeName) return `${this.baseClass} ${this.baseClass}--active`
return `${this.baseClass}`
},
itemClass() {
if (this.open) return `${this.baseClass}__sub-icon transform rotate-180`
return `${this.baseClass}__sub-icon`
},
listClass() {
if (this.open) return `${this.baseClass}__sub-open`
return ""
},
},
methods: {
toggle() {
this.open = !this.open
}
}
}
</script>
<template>
<li>
<span
:class="containerClass"
@click="toggle"
>
<div :class="`${baseClass}__icon`"> <i :icon="menuItem.icon" /> </div>
<div :class="`${baseClass}__title`">
{{ menuItem.show }}
<div :class="itemClass"> <i icon="chevron-down" /> </div>
</div>
</span>
<ul :class="listClass">
<MenuItem
v-for="item in menuItem.children"
:key="JSON.stringify(item)"
:baseClass="baseClass"
:type="item.type"
:menuItem="item"
/>
</ul>
</li>
</template>

View File

@ -0,0 +1,89 @@
<script>
import { cond, equals, T } from 'ramda'
import { createIcons, icons } from "lucide";
export default {
name: 'MenuItem',
props: {
type: {
type: String,
default: null
},
menuItem: {
type: {
line: String,
icon: String,
},
default: () => ({
link: "/"
})
},
baseClass: {
type: String,
default: ""
},
routeName: {
type: String,
default: ""
}
},
data() {
return {}
},
computed: {
linkClass() {
const menuRouterName = this.menuItem.router || ""
if (menuRouterName === this.routeName)
return `${this.baseClass} ${this.baseClass}--active`
return this.baseClass
},
_type() {
return cond([
[equals("simple_link"), (x) => x],
[equals("line"), (x) => x],
[equals("group"), (x) => x],
[T, () => "other"],
])(this.type)
}
},
mounted() {
this.icons()
},
updated() {
this.icons()
},
methods: {
icons() {
createIcons({ icons, nameAttr: "icon" })
}
}
}
</script>
<template>
<li v-if="_type === 'simple_link'">
<router-link
phx-hook="MenuLink"
:class="linkClass"
:to="menuItem.link"
>
<div :class="`${baseClass}__icon`">
<i :icon="menuItem?.icon" />
</div>
<div :class="`${baseClass}__title`">
{{ menuItem.show }}
</div>
</router-link>
</li>
<li v-if="_type === 'line'">
<hr class="m-4">
</li>
<slot />
<div v-if="_type === 'other'">
""
</div>
</template>

View File

@ -1,63 +0,0 @@
<script>
import MenuItem from './MenuItem.vue'
export default {
name: 'MobileMenu',
components: {
MenuItem
},
props: {
// eslint-disable-next-line vue/prop-name-casing
current_menu_item: {
type: String,
default: ""
},
// eslint-disable-next-line vue/prop-name-casing
menu_list: {
type: Array,
default: () => []
}
},
data() {
},
computed: {
menu_list_with_index() {
return this.menu_list.map((el, index) => ({...el, index}))
}
},
methods: {
rkey() {
return Math.random().toString(36).slice(4)
}
}
}
</script>
<template>
<div x-bind:class="mobile_menu_opened ? 'mobile-menu--active mobile-menu md:hidden' : 'mobile-menu md:hidden'">
<div class="scrollable">
<span
class="mobile-menu-toggler"
@click="mobile_menu_opened = !mobile_menu_opened"
>
<i
icon="x-circle"
class="w-8 h-8 text-white transform -rotate-90"
/>
</span>
<ul class="scrollable__content py-2">
<MenuItem
v-for="{index, ...menu_item} in menu_list_with_index"
:id="`main_menu_item_component_${index}`"
:key="rkey(index)"
base_class="menu"
:type="menu_item.type"
:menu_item="menu_item"
:current_menu_item="current_menu_item"
/>
</ul>
</div>
</div>
</template>

View File

@ -0,0 +1,14 @@
<script>
export default {
name: "PacksContainer",
}
</script>
<template>
<div class="grid grid-cols-12 gap-6 col-span-12 h-full">
<slot />
</div>
</template>

View File

@ -0,0 +1,14 @@
<script>
export default {
name: "PacksContainerBody"
}
</script>
<template>
<div class="">
ComponentTemplate
</div>
</template>

View File

@ -0,0 +1,18 @@
<script>
export default {
name: "PacksContainerControl",
}
</script>
<template>
<form
phx-submit="fetch_pack"
class="rounded-md bg-light px-4 pt-4 pb-6 gap-2 shadow lg:col-span-4 xl:col-span-3 2xl:col-span-2 col-span-12"
autocomplete="off"
>
<slot />
</form>
</template>

View File

@ -1,3 +1,64 @@
<script>
import ButtonModal from '@molecules/ButtonModal/ButtonModal.vue'
import Datepicker from "@molecules/VDatepicker/VDatepicker.vue"
import {mapGetters, mapMutations, mapActions} from 'vuex'
import Tabulator from "@molecules/VTabulator/VTabulator.vue"
import Spinner from "@molecules/VSpinner/VSpinner.vue"
import MachinesModal from "@molecules/MachinesModal/MachinesModal.vue"
export default {
name: 'LastPacks',
components: {
Datepicker,
ButtonModal,
Tabulator,
Spinner,
MachinesModal
},
data() {
return {
packNum: null,
}
},
computed: {
...mapGetters('last_packs', ['dtStart', 'dtFinish', 'imei', 'pageState', 'packsData']),
tabulatorOtps() {
return {
dataSource: this.packsData,
columns: [
{
title: "Нормер пакета",
field: "pack_number",
width: 150
},
{
title: "Количество записей",
field: "count",
width: 150
},
{
title: "Последняя дата пакета",
field: "pack_dt",
width: 150,
render: "{{ dt_format (dt_shift pack_dt 3 'hour') }}"
}
],
height: "550px"
}
}
},
methods: {
...mapMutations('last_packs', [
"setDtStart",
"setDtFinish",
"setImei"
]),
...mapActions('last_packs', ['uploadData'])
}
}
</script>
<template>
<div class="grid grid-cols-12 gap-6 col-span-12 h-full">
<form
@ -99,64 +160,3 @@
</div>
</div>
</template>
<script>
import ButtonModal from '@molecules/ButtonModal/ButtonModal.vue'
import Datepicker from "@molecules/VDatepicker/VDatepicker.vue"
import {mapGetters, mapMutations, mapActions} from 'vuex'
import Tabulator from "@molecules/VTabulator/VTabulator.vue"
import Spinner from "@molecules/VSpinner/VSpinner.vue"
import MachinesModal from "@molecules/MachinesModal/MachinesModal.vue"
export default {
name: 'LastPacks',
components: {
Datepicker,
ButtonModal,
Tabulator,
Spinner,
MachinesModal
},
data() {
return {
packNum: null,
}
},
computed: {
...mapGetters('last_packs', ['dtStart', 'dtFinish', 'imei', 'pageState', 'packsData']),
tabulatorOtps() {
return {
dataSource: this.packsData,
columns: [
{
title: "Нормер пакета",
field: "pack_number",
width: 150
},
{
title: "Количество записей",
field: "count",
width: 150
},
{
title: "Последняя дата пакета",
field: "pack_dt",
width: 150,
render: "{{ dt_format (dt_shift pack_dt 3 'hour') }}"
}
],
height: "550px"
}
}
},
methods: {
...mapMutations('last_packs', [
"setDtStart",
"setDtFinish",
"setImei"
]),
...mapActions('last_packs', ['uploadData'])
}
}
</script>

View File

@ -0,0 +1,43 @@
class ServiceOfLayout {
constructor(store) {
this.store = store
}
getHeight() {
return this.store.getters['layout/height']
}
getWidth() {
return this.store.getters['layout/width']
}
getIsOpenMenu() {
return this.store.getters['layout/isOpenMenu']
}
getIsShowMenu() {
return this.store.getters['layout/isShowMenu']
}
getIsEnabledMenu() {
return this.store.getters['layout/isEnabledMenu']
}
setIsShowMenu(upd) {
this.store.dispatch('layout/setIsShowMenu', upd)
}
setIsMobileMenuOpened(upd) {
this.store.dispatch('layout/setIsMobileMenuOpened', upd)
}
setIsOpenMenu(upd) {
this.store.dispatch('layout/setIsOpenMenu', upd)
}
setIsEnabledMenu(upd) {
this.store.dispatch('layout/setIsEnabledMenu', upd)
}
toggleMenu() {
this.store.dispatch('layout/toggleMenu')
}
}
export default ServiceOfLayout

View File

@ -6,7 +6,7 @@ import axios from 'axios';
import {orisMenu} from './menuList'
const initState = {
current_user: null,
currentUser: null,
errorMsg: null,
token: null,
inited: false,
@ -17,7 +17,7 @@ const state = {
...initState
};
const current_user = (token) => ({
const setCurrentUser = (token) => ({
position: "",
first_name: "Сергей",
last_name: "Шлыков",
@ -31,17 +31,17 @@ const getters = {
login: (state) => state.login,
password: (state) => state.password,
my_comp: (state) => state.my_comp,
current_user: (state) => state.current_user,
currentUser: (state) => state.currentUser,
menuList: () => orisMenu,
};
const mutations = {
initAuth: (state, token) => {
state.current_user = current_user(token)
state.currentUser = setCurrentUser(token)
state.token = token;
state.inited = true
},
set_my_comp: (state, upd) => state.my_comp,
set_my_comp: (state) => state.my_comp,
};

View File

@ -1,12 +1,11 @@
const orisMenu = [
{
type: "simple_link",
icon: "home",
show: "Главная",
link: "/",
router: "main"
router: "machines"
},
{
type: "simple_link",

View File

@ -1,58 +1,94 @@
import {initScreenListener, disableScreenListener} from "./stateHelpers.js";
const initState = {
height: 0,
width: 0,
isOpenMenu: false,
isOpenedMobileMenu: false,
isEnabledMenu: true,
isShowMenu: true,
initedScreenListener: false,
};
const state = {
height: 0,
width: 0,
inited: false,
isOpenMenu: false,
mobile_menu_opened: false,
show_menu: true,
is_enabled_menu: true
...initState
};
const getters = {
height: (state) => state.height,
width: (state) => state.width,
isOpenMenu: (state) => state.isOpenMenu,
menuWidth: (state) => null,
show_menu: (state) => state.show_menu,
is_enabled_menu: (state) => state.is_enabled_menu,
isShowMenu: (state) => state.isShowMenu,
isOpenedMobileMenu: (state) => state.isOpenedMobileMenu,
isEnabledMenu: (state) => state.isEnabledMenu,
initedScreenListener: (state) => state.initedScreenListener,
};
const mutations = {
enableScreenListener: (state) => {
state.height = window.innerHeight
state.width = window.innerWidth
const acc = !!state.inited
if (!acc) {
window.addEventListener("resize", (e) => {
state.height= e.target.innerHeight
state.width = e.target.innerWidth
});
state.inited = true
const inited = state.initedScreenListener
if (!inited) {
initScreenListener(state)
state.initedScreenListener = true
}
},
toggle_menu: (state) => {
state.show_menu = !state.show_menu
disableScreenListener: (state) => {
disableScreenListener(state)
},
set_mobile_menu_opened: (state, upd) => {
state.mobile_menu_opened = upd
toggleMenu: (state) => {
state.isShowMenu = !state.isShowMenu
},
set_show_menu: (state, upd) => {
state.show_menu = upd
saveIsMobileMenuOpened: (state, upd) => {
state.isOpenedMobileMenu = upd
},
saveIsShowMenu: (state, upd) => {
state.isShowMenu = upd
},
resetStore: (state) => {
for (let key in state) {
state[key] = initState[key]
}
},
saveIsEnabledMenu: (state, upd) => {
state.isEnabledMenu = upd
},
saveIsOpenMenu: (state, upd) => {
state.isOpenMenu = upd
},
};
const actions = {
initScreenSizeRecalc: ({commit}) => {
initScreenSizeListener: ({commit}) => {
commit("enableScreenListener")
},
disableScreenListener: ({commit}) => {
commit("disableScreenListener")
},
resetStore: ({commit}) => {
commit("resetStore")
},
toggleMenu: ({commit}) => {
commit("toggleMenu")
},
setIsShowMenu: ({commit}, upd) => {
commit("saveIsShowMenu", upd)
},
setIsMobileMenuOpened: ({commit}, upd) => {
commit("saveIsMobileMenuOpened", upd)
},
setIsEnabledMenu: ({commit}, upd) => {
commit("saveIsEnabledMenu", upd)
},
setIsOpenMenu: ({commit}, upd) => {
commit("saveIsOpenMenu", upd)
},
};
export const store = {
namespaced: true,
state,
getters,
mutations,
actions,
namespaced: true,
state,
getters,
mutations,
actions,
};

View File

@ -1,126 +0,0 @@
const orisMenu = [
{
type: "simple_link",
icon: "home",
show: "Главная",
link: "/",
router: "main"
},
{
type: "simple_link",
icon: "book-marked",
show: "Последние пакеты",
link: "/html/last_packs",
router: "last_packs"
},
{
type: "simple_link",
icon: "box",
show: "Коммуникационный",
link: "/html/controll_com_service",
router: "comm"
},
{
type: "simple_link",
icon: "book-key",
show: "Последние пакеты(по номеру)",
link: "/html/last_packs_by_pack",
router: "last_pack_number"
},
{
type: "simple_link",
icon: "beaker",
show: "Топливо",
link: "/html/live_fuel",
router: "fuel"
},
{type: "simple_link", icon: "timer", show: "Крон", link: "/html/cron", router: "cron"},
{
type: "group",
show: "Справочники",
icon: "book",
children: [
{type: "simple_link", icon: "book-open", show: "ICCID", link: "/html/fetch_iccid"},
{
type: "simple_link",
icon: "book-open",
show: "Устройства",
link: "/html/fetch_askr_devices"
}
]
},
{
type: "group",
show: "Пакеты",
icon: "cast",
children:
[
{
type: "simple_link",
icon: "curly-braces",
show: "Свободный поиск",
link: "/html/free_packs"
}
]
},
{type: "line"},
{
type: "group",
show: "Логи",
icon: "fingerprint",
children: [
{
type: "simple_link",
icon: "code",
show: "Файлы логов",
link: "/html/file_viewer/logs"
},
{
type: "simple_link",
icon: "code",
show: "Файлы машин",
link: "/html/file_viewer/machine_data"
}
]
},
{type: "line"},
{
type: "group",
show: "Работоспособность сервсисов",
icon: "cloud-cog",
children: [
{
type: "simple_link",
icon: "cpu",
show: "ДИАП",
link: "/admin_panel/services/diap"
},
{type: "simple_link", icon: "table", show: "Сервисы", link: "/html/communications"},
]
},
{
type: "group",
show: "Админка",
icon: "hand-metal",
children: [
{
type: "simple_link",
icon: "haze",
show: "Сервис управления",
link: "/admin_panel/auth_service"
},
{
type: "simple_link",
icon: "line-chart",
show: "Анализ топлива",
link: "/admin_panel/fuel_analyzer"
},
{type: "simple_link", icon: "newspaper", show: "Обновления", link: "/html/updates"},
{type: "simple_link", icon: "boxes", show: "Пакеты", link: "/admin_panel/packs"}
]
},
{type: "line"},
{type: "simple_link", icon: "newspaper", show: "Обновления", link: "/html/updates", router: "news"},
]
export {orisMenu}

View File

@ -0,0 +1,19 @@
const initScreenListener = (state) => {
state.height = window.innerHeight
state.width = window.innerWidth
window.addEventListener("resize", (e) => {
state.height= e.target.innerHeight
state.width = e.target.innerWidth
});
return "ok"
}
const disableScreenListener = (state) => {
window.removeEventListener("resize", () => {
state.height = null
state.width = null
});
return "ok"
}
export { initScreenListener, disableScreenListener }

View File

@ -7,7 +7,7 @@
&.mobile-menu--active {
&:before {
content: "";
@apply visible opacity-100;
@apply visible opacity-100 h-[110vh];
}
.scrollable {
@apply ml-0 overflow-y-auto;

View File

@ -1,10 +1,10 @@
import {test, describe, expect} from 'vitest'
import { mount} from '@vue/test-utils'
import Button from '@atoms/VButton.vue';
import VButton from '@atoms/VButton.vue';
describe('Accordion', () => {
test('Accordion mounted', () => {
const wrapper = mount(Button)
describe('VButton', () => {
test('VButton mounted', () => {
const wrapper = mount(VButton)
expect(wrapper.exists()).toBe(true)
})
})

View File

@ -0,0 +1,27 @@
import {test, describe, expect, } from 'vitest'
import { shallowMount} from '@vue/test-utils'
import AppContainer from '@frames/AppContainer/AppContainer.vue';
import {createStore} from "vuex";
import { store as layout } from '@/store/modules/layout';
import { store as auth } from '@/store/modules/auth';
describe('AppContainer', () => {
const store = createStore({
modules: {
layout,
auth
},
})
test('AppContainer mounted', () => {
const wrapper = shallowMount(AppContainer,
{
global: {
mocks: {
$store: store,
},
}}
)
expect(wrapper.exists()).toBe(true)
})
})

View File

@ -0,0 +1,10 @@
import {test, describe, expect, } from 'vitest'
import { shallowMount} from '@vue/test-utils'
import AppContainerBreadcrumbs from '@frames/AppContainer/AppContainerBreadcrumbs.vue';
describe('AppContainerBreadcrumbs', () => {
test('AppContainerBreadcrumbs mounted', () => {
const wrapper = shallowMount(AppContainerBreadcrumbs)
expect(wrapper.exists()).toBe(true)
})
})

View File

@ -0,0 +1,10 @@
import {test, describe, expect, } from 'vitest'
import { shallowMount} from '@vue/test-utils'
import AppContainerHeader from '@frames/AppContainer/AppContainerHeader.vue';
describe('AppContainerHeader', () => {
test('AppContainerHeader mounted', () => {
const wrapper = shallowMount(AppContainerHeader)
expect(wrapper.exists()).toBe(true)
})
})

View File

@ -0,0 +1,16 @@
import {test, describe, expect, } from 'vitest'
import { shallowMount} from '@vue/test-utils'
import AppContainerLeftPanel from '@frames/AppContainer/AppContainerLeftPanel.vue';
import Router from "@router"
describe('AppContainerLeftPanel', () => {
test('AppContainerLeftPanel mounted', () => {
const wrapper = shallowMount(AppContainerLeftPanel,
{
global: {
plugins: [Router]
}
})
expect(wrapper.exists()).toBe(true)
})
})

View File

@ -0,0 +1,15 @@
import {test, describe, expect, } from 'vitest'
import { shallowMount} from '@vue/test-utils'
import AppContainerMobileMenu from '@frames/AppContainer/AppContainerMobileMenu.vue';
describe('AppContainerMobileMenu', () => {
test('AppContainerMobileMenu mounted', () => {
const wrapper = shallowMount(AppContainerMobileMenu, {
props: {
isOpenedMobileMenu: false,
setIsMobileMenuOpened: () => {},
}
})
expect(wrapper.exists()).toBe(true)
})
})

View File

@ -0,0 +1,20 @@
import {test, describe, expect, } from 'vitest'
import { shallowMount} from '@vue/test-utils'
import AppContainerRightPanel from '@frames/AppContainer/AppContainerRightPanel.vue';
import Router from "@router"
describe('AppContainerRightPanel', () => {
test('AppContainerRightPanel mounted', () => {
const wrapper = shallowMount(AppContainerRightPanel, {
props: {
currentUser: {
id: 1,
}
},
global: {
plugins: [Router]
}
})
expect(wrapper.exists()).toBe(true)
})
})

View File

@ -0,0 +1,15 @@
import {test, describe, expect, } from 'vitest'
import { shallowMount} from '@vue/test-utils'
import MenuList from '@frames/AppContainer/MenuList/MenuList.vue';
describe('MenuList', () => {
test('MenuList mounted', () => {
const wrapper = shallowMount(MenuList, {
props: {
routeName: ""
}
})
expect(wrapper.exists()).toBe(true)
})
})

View File

@ -0,0 +1,11 @@
import {test, describe, expect, } from 'vitest'
import { shallowMount} from '@vue/test-utils'
import MenuListItem from '@frames/AppContainer/MenuList/MenuListItem.vue';
describe('MenuListItem', () => {
test('MenuListItem mounted', () => {
const wrapper = shallowMount(MenuListItem)
expect(wrapper.exists()).toBe(true)
})
})

View File

@ -0,0 +1,10 @@
import {test, describe, expect, } from 'vitest'
import { shallowMount} from '@vue/test-utils'
import MenuListItemCopy from '@frames/AppContainer/MenuList/MenuListItemCopy.vue';
describe('MenuListItemCopy', () => {
test('MenuListItemCopy mounted', () => {
const wrapper = shallowMount(MenuListItemCopy)
expect(wrapper.exists()).toBe(true)
})
})

View File

@ -0,0 +1,10 @@
import {test, describe, expect, } from 'vitest'
import { shallowMount} from '@vue/test-utils'
import MenuListItemGroup from '@frames/AppContainer/MenuList/MenuListItemGroup.vue';
describe('MenuListItemGroup', () => {
test('MenuListItemGroup mounted', () => {
const wrapper = shallowMount(MenuListItemGroup)
expect(wrapper.exists()).toBe(true)
})
})

View File

@ -0,0 +1,17 @@
import {test, describe, expect, } from 'vitest'
import { shallowMount} from '@vue/test-utils'
import MenuListItemImplementation from '@frames/AppContainer/MenuList/MenuListItemImplementation.vue';
import Router from "@router"
describe('MenuListItemImplementation', () => {
test('MenuListItemImplementation mounted', () => {
const wrapper = shallowMount(MenuListItemImplementation,
{
global: {
plugins: [Router]
}
})
expect(wrapper.exists()).toBe(true)
})
})

View File

@ -0,0 +1,10 @@
import {test, describe, expect} from 'vitest'
import { mount} from '@vue/test-utils'
import PacksContainer from '@frames/PacksContainer/PacksContainer.vue';
describe('PacksContainer', () => {
test('PacksContainer mounted', () => {
const wrapper = mount(PacksContainer)
expect(wrapper.exists()).toBe(true)
})
})

View File

@ -0,0 +1,10 @@
import {test, describe, expect} from 'vitest'
import { mount} from '@vue/test-utils'
import PacksContainerBody from '@frames/PacksContainer/PacksContainerBody.vue';
describe('PacksContainerBody', () => {
test('PacksContainerBody mounted', () => {
const wrapper = mount(PacksContainerBody)
expect(wrapper.exists()).toBe(true)
})
})

View File

@ -0,0 +1,10 @@
import {test, describe, expect} from 'vitest'
import { mount} from '@vue/test-utils'
import PacksContainerControl from '@frames/PacksContainer//PacksContainerControl.vue';
describe('PacksContainerControl', () => {
test('PacksContainerControl mounted', () => {
const wrapper = mount(PacksContainerControl)
expect(wrapper.exists()).toBe(true)
})
})

View File

@ -0,0 +1,51 @@
import {expect, describe, test, beforeEach} from 'vitest'
import ServiceOfLayout from "@services/ServiceOfLayout.js"
import { createStore } from "vuex"
import { store as layout } from '@/store/modules/layout';
describe('test ServiceOfLayout', () => {
const store = createStore({
modules: {
layout
}
})
beforeEach(() => {
store.dispatch('layout/resetStore')
})
const serviceOfLayout = new ServiceOfLayout(store)
test('test ServiceOfLayout exist', async () => {
expect(serviceOfLayout).toBeTruthy()
expect(serviceOfLayout.getHeight()).toBe(0)
expect(serviceOfLayout.getWidth()).toBe(0)
expect(serviceOfLayout.getIsOpenMenu()).toBe(false)
expect(serviceOfLayout.getIsEnabledMenu()).toBe(true)
expect(serviceOfLayout.getIsShowMenu()).toBe(true)
})
test('test toggleMenu', async () => {
serviceOfLayout.toggleMenu()
const result = store.getters['layout/isShowMenu']
expect(result).toBe(false)
})
test('setIsShowMenu', async () => {
serviceOfLayout.setIsShowMenu(false)
const result = serviceOfLayout.getIsShowMenu()
expect(result).toBe(false)
})
test('setIsOpenMenu', async () => {
serviceOfLayout.setIsOpenMenu(true)
const result = serviceOfLayout.getIsOpenMenu()
expect(result).toBe(true)
})
test('setIsEnabledMenu', async () => {
serviceOfLayout.setIsEnabledMenu(false)
const result = serviceOfLayout.getIsEnabledMenu()
expect(result).toBe(false)
})
})

View File

@ -0,0 +1,53 @@
import {expect, describe, test, beforeEach} from 'vitest'
import { createStore } from "vuex"
import { store as layout } from '@/store/modules/layout';
describe('test of layout store', () => {
const store = createStore({
modules: {
layout
}
})
beforeEach(() => {
store.dispatch('layout/resetStore')
})
test('test layout store exist', async () => {
expect(store).toBeTruthy()
expect(store.getters['layout/isOpenedMobileMenu']).toBe(false)
expect(store.getters['layout/isEnabledMenu']).toBe(true)
})
test('test toggleMenu', () => {
store.dispatch('layout/toggleMenu')
const result = store.getters['layout/isShowMenu']
expect(result).toBe(false)
})
test('test setIsShowMenu', () => {
store.dispatch('layout/setIsShowMenu', false)
const result = store.getters['layout/isShowMenu']
expect(result).toBe(false)
})
test('test setIsMobileMenuOpened', () => {
store.dispatch('layout/setIsMobileMenuOpened', true)
const result = store.getters['layout/isOpenedMobileMenu']
expect(result).toBe(true)
})
test('test resetStore', () => {
store.dispatch('layout/resetStore')
const result = store.getters['layout/isShowMenu']
expect(result).toBe(true)
})
test('test initScreenSizeListener', () => {
store.dispatch('layout/initScreenSizeListener')
const result = store.getters['layout/initedScreenListener']
expect(result).toBe(true)
})
})