mirror of https://github.com/4xmen/xshop.git
xController list + template-list
parent
5a3deaa61b
commit
56110bb254
@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App;
|
||||||
|
|
||||||
|
interface SafeController
|
||||||
|
{
|
||||||
|
//
|
||||||
|
public function createOrUpdate();
|
||||||
|
}
|
@ -0,0 +1,122 @@
|
|||||||
|
<template>
|
||||||
|
<div id="currency">
|
||||||
|
<input @keyup="keyup" :id="xid" :placeholder="xtitle" :class="getClass" type="text" v-model="val">
|
||||||
|
<input type="hidden" :name="xname" :value="noComma">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
function commafy(num) {
|
||||||
|
if (typeof num !== 'string') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
let str = uncommafy(num.toString()).split('.');
|
||||||
|
if (str[0].length >= 5) {
|
||||||
|
str[0] = str[0].replace(/(\d)(?=(\d{3})+$)/g, '$1,');
|
||||||
|
}
|
||||||
|
if (str[1] && str[1].length >= 5) {
|
||||||
|
str[1] = str[1].replace(/(\d{3})/g, '$1 ');
|
||||||
|
}
|
||||||
|
return str.join('.');
|
||||||
|
}
|
||||||
|
|
||||||
|
function uncommafy(txt) {
|
||||||
|
return txt.split(',').join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "curency-input",
|
||||||
|
components: {},
|
||||||
|
data: () => {
|
||||||
|
return {
|
||||||
|
val: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['update:modelValue'],
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 'nop',
|
||||||
|
},
|
||||||
|
xname: {
|
||||||
|
default: "",
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
xtitle: {
|
||||||
|
default: "",
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
xvalue: {
|
||||||
|
default: "",
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
xid: {
|
||||||
|
default: "",
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
customClass: {
|
||||||
|
default: "",
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
err: {
|
||||||
|
default: false,
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
|
|
||||||
|
updateKey: {
|
||||||
|
default: function () {
|
||||||
|
|
||||||
|
},
|
||||||
|
type: Function,
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
|
||||||
|
if (this.modelValue !== 'nop') {
|
||||||
|
|
||||||
|
if (typeof this.modelValue == 'number') {
|
||||||
|
this.val = commafy(this.modelValue.toString());
|
||||||
|
} else {
|
||||||
|
this.val = commafy(this.modelValue);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (typeof this.xvalue == 'number') {
|
||||||
|
this.val = commafy(this.xvalue.toString());
|
||||||
|
} else {
|
||||||
|
this.val = commafy(this.xvalue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
noComma: function () {
|
||||||
|
const n = uncommafy(this.val);
|
||||||
|
if (this.modelValue != null) {
|
||||||
|
this.$emit('update:modelValue', n);
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
},
|
||||||
|
getClass: function () {
|
||||||
|
if (this.err == true || (typeof this.err == 'String' && this.err.trim() == '1')) {
|
||||||
|
return 'form-control is-invalid ' + this.customClass;
|
||||||
|
}
|
||||||
|
return 'form-control ' + this.customClass;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
keyup: function () {
|
||||||
|
this.val = commafy(this.val);
|
||||||
|
if (typeof this.update === 'function') {
|
||||||
|
this.update(this.updateKey, parseInt(this.noComma));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
@ -1,23 +1,100 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="container">
|
<div id="example">
|
||||||
<div class="row justify-content-center">
|
<div class="p-2">
|
||||||
<div class="col-md-8">
|
<currency-input v-model="p"></currency-input>
|
||||||
<div class="card">
|
<span>
|
||||||
<div class="card-header">Example Component</div>
|
{{ p }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="p-2">
|
||||||
|
<font-awesome-icon-picker v-model="f"></font-awesome-icon-picker>
|
||||||
|
<span>
|
||||||
|
{{ f }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="p-2">
|
||||||
|
<remix-icon-picker v-model="r"></remix-icon-picker>
|
||||||
|
<span>
|
||||||
|
{{ r }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card-body">
|
<div class="p-2">
|
||||||
I'm an example component.
|
<searchable-multi-select :items="cats" v-model="mss"></searchable-multi-select>
|
||||||
|
<span>
|
||||||
|
{{ mss }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="p-2">
|
||||||
|
<vue-date-range-picker v-model="dpr"></vue-date-range-picker>
|
||||||
|
<span>
|
||||||
|
{{ dpr }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="p-2">
|
||||||
|
<vue-date-time-picker v-model="dp"></vue-date-time-picker>
|
||||||
|
<span>
|
||||||
|
{{ dp }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="p-2">
|
||||||
|
<vue-date-time-picker v-model="dpt" :timepicker="true"></vue-date-time-picker>
|
||||||
|
<span>
|
||||||
|
{{ dpt }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="p-2">
|
||||||
|
<increment v-model="i"></increment>
|
||||||
|
<span>
|
||||||
|
{{ i }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
import CurrencyInput from "./CurrencyInput.vue";
|
||||||
mounted() {
|
import fontAwesomeIconPicker from "./FontAwesomeIconPicker.vue";
|
||||||
console.log('Component mounted.')
|
import remixIconPicker from "./RemixIconPicker.vue";
|
||||||
|
import searchableSelect from "./SearchableSelect.vue";
|
||||||
|
import searchableMultiSelect from "./searchableMultiSelect.vue";
|
||||||
|
import vueDateTimePicker from "./vueDateTimePicker.vue";
|
||||||
|
import vueDateRangePicker from "./vueDateRangePicker.vue";
|
||||||
|
import Increment from "./Increment.vue";
|
||||||
|
export default {
|
||||||
|
name: "example",
|
||||||
|
components: {
|
||||||
|
CurrencyInput, fontAwesomeIconPicker, remixIconPicker,Increment,
|
||||||
|
searchableSelect, searchableMultiSelect, vueDateRangePicker, vueDateTimePicker
|
||||||
|
},
|
||||||
|
data: () => {
|
||||||
|
return {
|
||||||
|
p: 100000,
|
||||||
|
f: 'fa fa-user',
|
||||||
|
r: 'ri-user-line',
|
||||||
|
ss: 3,
|
||||||
|
mss: [3,11,7],
|
||||||
|
dpr:[],
|
||||||
|
dp: null,
|
||||||
|
dpt: null,
|
||||||
|
i:7,
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
cats: {
|
||||||
|
default: [],
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
},
|
||||||
|
computed: {},
|
||||||
|
methods: {}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
#example {
|
||||||
|
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,105 @@
|
|||||||
|
<template>
|
||||||
|
<div id="inc">
|
||||||
|
<div class="btn btn-outline-danger" @click="dec">
|
||||||
|
<i class="ri-subtract-line"></i>
|
||||||
|
</div>
|
||||||
|
<input type="text" class="form-control"
|
||||||
|
v-model="val"
|
||||||
|
@keyup.up="inc"
|
||||||
|
@keyup.down="dec">
|
||||||
|
<div class="btn btn-outline-success" @click="inc">
|
||||||
|
<i class="ri-add-line"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "inc",
|
||||||
|
components: {},
|
||||||
|
data: () => {
|
||||||
|
return {
|
||||||
|
val: 0,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['update:modelValue'],
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: [Number, NaN],
|
||||||
|
default: NaN,
|
||||||
|
},
|
||||||
|
xvalue: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
xmin: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
xmax: {
|
||||||
|
type: Number,
|
||||||
|
default: 100
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (!isNaN(this.modelValue)) {
|
||||||
|
this.val = this.modelValue;
|
||||||
|
}else{
|
||||||
|
this.val = this.xvalue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {},
|
||||||
|
methods: {
|
||||||
|
inc(e){
|
||||||
|
e.preventDefault();
|
||||||
|
if (this.val < this.xmax){
|
||||||
|
this.val++;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dec(e){
|
||||||
|
e.preventDefault();
|
||||||
|
if (this.val > this.xmin){
|
||||||
|
this.val--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
val(newValue) {
|
||||||
|
if (parseInt(this.val) > this.xmax){
|
||||||
|
this.val = this.xmax;
|
||||||
|
}
|
||||||
|
if (parseInt(this.val) < this.xmin){
|
||||||
|
this.val = this.xmin;
|
||||||
|
}
|
||||||
|
if (!isNaN(this.modelValue)) {
|
||||||
|
this.$emit('update:modelValue', newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
#inc {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 2fr 1fr;
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
|
||||||
|
#inc .form-control {
|
||||||
|
border-radius: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#inc div:first-child {
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#inc div:last-child {
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
</style>
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,199 @@
|
|||||||
|
<template>
|
||||||
|
<div id="searchable-select">
|
||||||
|
<div id="ss-modal" @click.self="hideModal" v-if="modalShow">
|
||||||
|
<div id="ss-selector">
|
||||||
|
<div class="p-2">
|
||||||
|
<input type="text" class="form-control" v-model="q" :placeholder="xtitle">
|
||||||
|
</div>
|
||||||
|
<div class="p-2">
|
||||||
|
<ul id="vue-search-list" class="list-group list-group-flush">
|
||||||
|
<template v-for="item in items">
|
||||||
|
<li
|
||||||
|
v-if="(q != '' && item[titleField].indexOf(q) != -1) || (q == '')"
|
||||||
|
@click="selecting(item[valueField])"
|
||||||
|
:class="`list-group-item ${val == item[valueField]?'selected':''}`">
|
||||||
|
{{item[titleField]}}
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<div class="input-group-prepend" id="vue-search-btn" @click="showModal" >
|
||||||
|
<span class="input-group-text" id="basic-addon1">
|
||||||
|
<i class="ri-search-2-line"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<select :id="xid" :class="getClass" v-model="val" @change="select">
|
||||||
|
<option value=""> {{xtitle}} </option>
|
||||||
|
<option v-for="item in items"
|
||||||
|
:value="item[valueField]"
|
||||||
|
:selected="item[valueField] == val">
|
||||||
|
{{item[titleField]}}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input type="hidden" :name="xname" :value="val">
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "searchable-select",
|
||||||
|
components: {},
|
||||||
|
data: () => {
|
||||||
|
return {
|
||||||
|
modalShow: false, // modal handle
|
||||||
|
q: '', // search query
|
||||||
|
val:'',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['update:modelValue'],
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
default: NaN,
|
||||||
|
},
|
||||||
|
items:{
|
||||||
|
required: true,
|
||||||
|
default: [],
|
||||||
|
type: Array,
|
||||||
|
},
|
||||||
|
valueField:{
|
||||||
|
default: 'id',
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
titleField:{
|
||||||
|
default: 'title',
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
xname:{
|
||||||
|
default: "",
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
xtitle:{
|
||||||
|
default: "Please select",
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
xvalue:{
|
||||||
|
default: "",
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
xid:{
|
||||||
|
default: "",
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
customClass:{
|
||||||
|
default: "",
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
err:{
|
||||||
|
default: false,
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
|
|
||||||
|
onSelect:{
|
||||||
|
default: function () {
|
||||||
|
|
||||||
|
},
|
||||||
|
type: Function,
|
||||||
|
},
|
||||||
|
closeOnSelect: {
|
||||||
|
default: false,
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (!isNaN(this.modelValue)) {
|
||||||
|
this.val = this.modelValue;
|
||||||
|
}else{
|
||||||
|
this.val = this.xvalue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
getClass: function () {
|
||||||
|
if (this.err == true || ( typeof this.err == 'String' && this.err.trim() == '1' )) {
|
||||||
|
return 'form-control is-invalid '+this.customClass;
|
||||||
|
}
|
||||||
|
return 'form-control '+this.customClass;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
selecting(i){
|
||||||
|
this.val = i;
|
||||||
|
this.onSelect(this.val);
|
||||||
|
if (this.closeOnSelect){
|
||||||
|
this.hideModal();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
select(){
|
||||||
|
this.onSelect(this.val);
|
||||||
|
},
|
||||||
|
hideModal:function () {
|
||||||
|
this.modalShow = false;
|
||||||
|
},
|
||||||
|
showModal(){
|
||||||
|
this.modalShow = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
val(newValue) {
|
||||||
|
if (!isNaN(this.modelValue)) {
|
||||||
|
this.$emit('update:modelValue', newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
#searchable-select {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#vue-search-btn{
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#vue-search-btn:hover .input-group-text{
|
||||||
|
background: darkred;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#ss-modal{
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 999;
|
||||||
|
background: #00000033;
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ss-selector{
|
||||||
|
height: 60vh;
|
||||||
|
border-radius: 7px;
|
||||||
|
min-width: 350px;
|
||||||
|
width: 400px;
|
||||||
|
max-width: 90%;
|
||||||
|
margin: 20vh auto;
|
||||||
|
background: #ffffff99;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#vue-search-list{
|
||||||
|
height: calc(60vh - 90px);
|
||||||
|
overflow-x: auto ;
|
||||||
|
}
|
||||||
|
|
||||||
|
#vue-search-list .list-group-item:hover{
|
||||||
|
background: deepskyblue;
|
||||||
|
}
|
||||||
|
#vue-search-list .list-group-item.selected{
|
||||||
|
background: dodgerblue;
|
||||||
|
color: white;;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,229 @@
|
|||||||
|
<template>
|
||||||
|
<div id="searchable-select">
|
||||||
|
<div id="ss-modal" @click.self="hideModal" v-if="modalShow">
|
||||||
|
<div id="ss-selector">
|
||||||
|
<div class="p-2">
|
||||||
|
<input type="text" class="form-control" v-model="q" :placeholder="xtitle">
|
||||||
|
</div>
|
||||||
|
<div class="p-2">
|
||||||
|
<ul id="vue-search-list" class="list-group list-group-flush">
|
||||||
|
<template v-for="item in items">
|
||||||
|
<li
|
||||||
|
v-if="(q != '' && item[titleField].toLocaleLowerCase().indexOf(q.toLocaleLowerCase()) != -1) || (q == '')"
|
||||||
|
@click="selecting(item[valueField])"
|
||||||
|
:class="`list-group-item ${val.indexOf(item[valueField]) !== -1?'selected':''}`">
|
||||||
|
{{ item[titleField] }}
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<div class="input-group-prepend" id="vue-search-btn" @click="showModal">
|
||||||
|
<span class="input-group-text" id="basic-addon1">
|
||||||
|
<i class="ri-check-line"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="form-control" id="vue-lst" @click.self="showModal">
|
||||||
|
<template v-for="item in items">
|
||||||
|
<span class="tag-select" v-if=" val.indexOf(item[valueField]) !== -1" >
|
||||||
|
{{ item[titleField] }}
|
||||||
|
<i class="ri-close-line" @click="rem(item[valueField])"></i>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input type="hidden" :name="xname" :value="JSON.stringify(val)">
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "searchable-select",
|
||||||
|
components: {},
|
||||||
|
data: () => {
|
||||||
|
return {
|
||||||
|
modalShow: false, // modal handle
|
||||||
|
q: '', // search query
|
||||||
|
val: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['update:modelValue'],
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
default: 'nop',
|
||||||
|
},
|
||||||
|
items: {
|
||||||
|
required: true,
|
||||||
|
default: [],
|
||||||
|
type: Array,
|
||||||
|
},
|
||||||
|
valueField: {
|
||||||
|
default: 'id',
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
titleField: {
|
||||||
|
default: 'title',
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
xname: {
|
||||||
|
default: "",
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
xtitle: {
|
||||||
|
default: "Please select",
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
xvalue: {
|
||||||
|
default: [],
|
||||||
|
type: Array,
|
||||||
|
},
|
||||||
|
xid: {
|
||||||
|
default: "",
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
customClass: {
|
||||||
|
default: "",
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
err: {
|
||||||
|
default: false,
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
|
|
||||||
|
onSelect: {
|
||||||
|
default: function () {
|
||||||
|
|
||||||
|
},
|
||||||
|
type: Function,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (this.modelValue != 'nop') {
|
||||||
|
this.val = this.modelValue;
|
||||||
|
}else{
|
||||||
|
this.val = this.xvalue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
getClass: function () {
|
||||||
|
if (this.err == true || (typeof this.err == 'String' && this.err.trim() == '1')) {
|
||||||
|
return 'form-control is-invalid ' + this.customClass;
|
||||||
|
}
|
||||||
|
return 'form-control ' + this.customClass;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
rem(i){
|
||||||
|
this.val.splice(this.val.indexOf(i),1);
|
||||||
|
this.onSelect(this.val,i);
|
||||||
|
},
|
||||||
|
selecting(i) {
|
||||||
|
if (this.val.indexOf(i) == -1){
|
||||||
|
this.val.push(i);
|
||||||
|
}else{
|
||||||
|
this.val.splice(this.val.indexOf(i),1);
|
||||||
|
}
|
||||||
|
this.onSelect(this.val,i);
|
||||||
|
},
|
||||||
|
select() {
|
||||||
|
this.onSelect(this.val);
|
||||||
|
},
|
||||||
|
hideModal: function () {
|
||||||
|
this.modalShow = false;
|
||||||
|
},
|
||||||
|
showModal() {
|
||||||
|
this.modalShow = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
val(newValue) {
|
||||||
|
if (this.modelValue != 'nop') {
|
||||||
|
this.$emit('update:modelValue', newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
#searchable-select {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#vue-search-btn {
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#vue-search-btn:hover .input-group-text {
|
||||||
|
background: deepskyblue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#ss-modal {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 999;
|
||||||
|
background: #00000033;
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ss-selector {
|
||||||
|
height: 60vh;
|
||||||
|
border-radius: 7px;
|
||||||
|
min-width: 350px;
|
||||||
|
width: 400px;
|
||||||
|
max-width: 90%;
|
||||||
|
margin: 20vh auto;
|
||||||
|
background: #282D47;
|
||||||
|
box-shadow: 0 0 4px gray;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#vue-search-list {
|
||||||
|
height: calc(60vh - 90px);
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#vue-search-list .list-group-item:hover {
|
||||||
|
background: #6610F2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#vue-search-list .list-group-item.selected {
|
||||||
|
background: darkred;
|
||||||
|
color: white;;
|
||||||
|
}
|
||||||
|
|
||||||
|
#vue-lst {
|
||||||
|
user-select: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.tag-select{
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 4px 0 20px ;
|
||||||
|
margin-right: 5px;
|
||||||
|
background: #282c34dd;
|
||||||
|
color: white;
|
||||||
|
position: relative;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-select i{
|
||||||
|
font-size: 20px;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: -5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-select i:hover{
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
</style>
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,374 @@
|
|||||||
|
<template>
|
||||||
|
<div id="vue-a1-calendar">
|
||||||
|
<div v-if="pDate !== null">
|
||||||
|
|
||||||
|
<div class="cal-grid">
|
||||||
|
<div @click="prvMonth">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
d="M10.0859 12.0001L5.29297 16.793L6.70718 18.2072L12.9143 12.0001L6.70718 5.79297L5.29297 7.20718L10.0859 12.0001ZM17.0001 6.00008L17.0001 18.0001H15.0001L15.0001 6.00008L17.0001 6.00008Z"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ pDate.persianMonthNames[cMonth] }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="number" min="0" v-model="cYear">
|
||||||
|
</div>
|
||||||
|
<div @click="nxtMonth">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
d="M13.9142 12.0001L18.7071 7.20718L17.2929 5.79297L11.0858 12.0001L17.2929 18.2072L18.7071 16.793L13.9142 12.0001ZM7 18.0001V6.00008H9V18.0001H7Z"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<table id="vue-cal-table">
|
||||||
|
<tr>
|
||||||
|
<th @click="selectByWeekDay(0)">
|
||||||
|
{{ __("Saturday") }}
|
||||||
|
</th>
|
||||||
|
<th @click="selectByWeekDay(1)">
|
||||||
|
{{ __("Sunday") }}
|
||||||
|
</th>
|
||||||
|
<th @click="selectByWeekDay(2)">
|
||||||
|
{{ __("Monday") }}
|
||||||
|
</th>
|
||||||
|
<th @click="selectByWeekDay(3)">
|
||||||
|
{{ __("Tuesday") }}
|
||||||
|
</th>
|
||||||
|
<th @click="selectByWeekDay(4)">
|
||||||
|
{{ __("Wednesday") }}
|
||||||
|
</th>
|
||||||
|
<th @click="selectByWeekDay(5)">
|
||||||
|
{{ __("Thursday") }}
|
||||||
|
</th>
|
||||||
|
<th @click="selectByWeekDay(6)">
|
||||||
|
{{ __("Friday") }}
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="(tr,i) in monthArray">
|
||||||
|
<td v-for="(td,j) in tr" @mouseenter="hover" @click="selecting(td)" :class="getClass(td)" :title="td.date+' '+td.pDate"
|
||||||
|
:data-timpstamp="td.unixTimeStamp">
|
||||||
|
{{ td.text }}
|
||||||
|
<template v-if="texts[fixDate(td.date)] !== undefined">
|
||||||
|
<div class="a1-badge" v-html="texts[fixDate(td.date)]"></div>
|
||||||
|
</template>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<input type="hidden" :value="JSON.stringify(selected)" :name="inputName">
|
||||||
|
</div>
|
||||||
|
<!-- <div>-->
|
||||||
|
<!-- {{JSON.stringify(selected)}}-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<div class="text-center mb-2">
|
||||||
|
{{ currentDate }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import persianDate from './libs/persian-date.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "vue-a1-calendar",
|
||||||
|
components: {},
|
||||||
|
data: () => {
|
||||||
|
return {
|
||||||
|
currentDate: '',
|
||||||
|
pDate: null,
|
||||||
|
cMonth: 4, // current month
|
||||||
|
cYear: 1402, // current year
|
||||||
|
selected: [],
|
||||||
|
selectedTimeStamp: [],
|
||||||
|
// translate simple
|
||||||
|
strings: {
|
||||||
|
Monday: 'دوشنبه',
|
||||||
|
Tuesday: 'سهشنبه',
|
||||||
|
Wednesday: 'چهارشنبه',
|
||||||
|
Thursday: 'پنجشنبه',
|
||||||
|
Friday: 'آدینه',
|
||||||
|
Saturday: 'شنبه',
|
||||||
|
Sunday: 'یکشنبه'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
month: {
|
||||||
|
default: 4,
|
||||||
|
type: Number,
|
||||||
|
},
|
||||||
|
year: {
|
||||||
|
default: 1370,
|
||||||
|
type: Number,
|
||||||
|
},
|
||||||
|
texts: {
|
||||||
|
default: {},
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
|
onSelect: {
|
||||||
|
type: Function,
|
||||||
|
default: function () {
|
||||||
|
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputName: {
|
||||||
|
type: String,
|
||||||
|
default: 'vueCalendarValue'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// initial values
|
||||||
|
|
||||||
|
this.cYear = this.year;
|
||||||
|
this.cMonth = this.month;
|
||||||
|
|
||||||
|
this.pDate = new persianDate();
|
||||||
|
|
||||||
|
}
|
||||||
|
,
|
||||||
|
computed: {
|
||||||
|
monthStart() {
|
||||||
|
return this.pDate.getPersianWeekDay(this.cYear + '/' + this.cMonth + '/1');
|
||||||
|
},
|
||||||
|
monthArray() {
|
||||||
|
let r = [[]];
|
||||||
|
let counter = this.monthStart;
|
||||||
|
|
||||||
|
|
||||||
|
for (let i = 1; i <= this.monthStart; i++) {
|
||||||
|
r[0].push({
|
||||||
|
date: '',
|
||||||
|
text: '',
|
||||||
|
unixTimeStamp: '',
|
||||||
|
pDate: '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 1; i <= this.pDate.pGetLastDayMonth(this.cMonth, this.cYear); i++) {
|
||||||
|
counter++;
|
||||||
|
r[r.length - 1].push({
|
||||||
|
date: this.pDate.imploiter(this.pDate.persian2Gregorian([this.cYear, this.cMonth, i]), '-'),
|
||||||
|
text: this.pDate.parseHindi(i),
|
||||||
|
unixTimeStamp: this.pDate.gDate2Timestamp(this.pDate.persian2Gregorian([this.cYear, this.cMonth, i])),
|
||||||
|
pDate: this.pDate.parseHindi(this.cYear + '/' + this.cMonth + '/' + this.pDate.make2number(i)),
|
||||||
|
});
|
||||||
|
if (counter % 7 === 0) {
|
||||||
|
r.push([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r;
|
||||||
|
|
||||||
|
}
|
||||||
|
,
|
||||||
|
}
|
||||||
|
,
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* Maybe one day translator
|
||||||
|
* @param txt
|
||||||
|
* @returns {*}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
__(txt) {
|
||||||
|
return this.strings[txt];
|
||||||
|
}
|
||||||
|
,
|
||||||
|
indexSelected(td) {
|
||||||
|
return this.selectedTimeStamp.indexOf(td.unixTimeStamp);
|
||||||
|
}
|
||||||
|
,
|
||||||
|
/**
|
||||||
|
* td object toggle select
|
||||||
|
* @param td
|
||||||
|
*/
|
||||||
|
selecting(td) {
|
||||||
|
if (td.date === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.indexSelected(td) === -1) {
|
||||||
|
this.selected.push(td);
|
||||||
|
this.selectedTimeStamp.push(td.unixTimeStamp);
|
||||||
|
this.onSelect(td);
|
||||||
|
} else {
|
||||||
|
this.selected.splice(this.indexSelected(td), 1);
|
||||||
|
this.selectedTimeStamp.splice(this.indexSelected(td), 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* calculate class by props of td
|
||||||
|
* @param td
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
getClass(td) {
|
||||||
|
let cls = '';
|
||||||
|
if (td.text === '') {
|
||||||
|
return '';
|
||||||
|
} else {
|
||||||
|
cls += ' selectable-td';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.indexSelected(td) !== -1) {
|
||||||
|
cls += ' selected-td';
|
||||||
|
}
|
||||||
|
|
||||||
|
return cls;
|
||||||
|
}
|
||||||
|
,
|
||||||
|
/**
|
||||||
|
* Selected dates by day week
|
||||||
|
* @param i
|
||||||
|
*/
|
||||||
|
selectByWeekDay(i) {
|
||||||
|
for (const tr of this.monthArray) {
|
||||||
|
if (tr[i] !== undefined) {
|
||||||
|
this.selecting(tr[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hover: function (e) {
|
||||||
|
this.currentDate = e.target.getAttribute('title');
|
||||||
|
},
|
||||||
|
nxtMonth() {
|
||||||
|
if (this.cMonth === 12) {
|
||||||
|
this.cYear++;
|
||||||
|
this.cMonth = 1;
|
||||||
|
} else {
|
||||||
|
this.cMonth++;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
prvMonth() {
|
||||||
|
if (this.cMonth === 1) {
|
||||||
|
this.cYear--;
|
||||||
|
this.cMonth = 12;
|
||||||
|
} else {
|
||||||
|
this.cMonth--;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fixDate(d) {
|
||||||
|
let t = d.split('-');
|
||||||
|
return parseInt(t[0]) + '-' + (parseInt(t[1]) < 10 ? '0' + parseInt(t[1]) : parseInt(t[1])) + '-' + (parseInt(t[2]) < 10 ? '0' + parseInt(t[2]) : parseInt(t[2]));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
#vue-a1-calendar {
|
||||||
|
border: 1px solid #44444444;
|
||||||
|
background: #fff;
|
||||||
|
direction: rtl;
|
||||||
|
user-select: none;
|
||||||
|
font-family: 'Vazir', 'Vazirmatn', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#vue-cal-table {
|
||||||
|
width: calc(100% - 2rem);
|
||||||
|
border-spacing: 0;
|
||||||
|
text-align: center;
|
||||||
|
margin: 0 auto 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#vue-cal-table th {
|
||||||
|
cursor: pointer;
|
||||||
|
background: dodgerblue;
|
||||||
|
border: 1px solid navy;
|
||||||
|
border-right: 0;
|
||||||
|
color: white;
|
||||||
|
width: calc(100% / 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#vue-cal-table td, #vue-cal-table th {
|
||||||
|
font-size: 22px;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#vue-cal-table th:first-child, #vue-cal-table td:first-child {
|
||||||
|
border-right: 1px solid navy;
|
||||||
|
}
|
||||||
|
|
||||||
|
#vue-cal-table td {
|
||||||
|
border: 1px solid navy;
|
||||||
|
border-right: 0;
|
||||||
|
border-top: 0;
|
||||||
|
position: relative;
|
||||||
|
height: 4rem;
|
||||||
|
transition: 200ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#vue-cal-table td.selected-td {
|
||||||
|
background: rgba(144, 238, 144, 0.82);
|
||||||
|
}
|
||||||
|
|
||||||
|
#vue-cal-table td.selectable-td {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cal-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
margin: 1rem;
|
||||||
|
background: #00a2a2;
|
||||||
|
color: white;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cal-grid div {
|
||||||
|
text-align: center;
|
||||||
|
padding: 1rem;
|
||||||
|
font-size: 25px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cal-grid svg {
|
||||||
|
width: 40px;
|
||||||
|
fill: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cal-grid div:first-child, .cal-grid div:last-child {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cal-grid input {
|
||||||
|
font-size: 25px;
|
||||||
|
text-align: center;
|
||||||
|
width: 100px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.a1-badge {
|
||||||
|
background: dodgerblue;
|
||||||
|
color: white;
|
||||||
|
border-radius: 3px;
|
||||||
|
position: absolute;
|
||||||
|
left: 2px;
|
||||||
|
top: 2px;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 7px;
|
||||||
|
opacity: .85;
|
||||||
|
transition: 400ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.selectable-td:hover {
|
||||||
|
background: #11111111;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.selectable-td.selected-td:hover {
|
||||||
|
background: rgba(25, 107, 73, .5) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
td:hover .a1-badge {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,7 @@
|
|||||||
|
document.addEventListener('click', function(e) {
|
||||||
|
if (e.target.classList.contains('delete-confirm')) {
|
||||||
|
if (!confirm('Are you sure you want to delete this item?')) { // WIP Need to translate
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
@ -1,5 +1,61 @@
|
|||||||
|
$pnl-brc-height: 50px;
|
||||||
|
|
||||||
#panel-breadcrumb{
|
#panel-breadcrumb{
|
||||||
height: 50px;
|
height: $pnl-brc-height;
|
||||||
background: #00000033;
|
background: #282d46;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
|
ul{
|
||||||
|
padding: 0;
|
||||||
|
display: grid;
|
||||||
|
grid-auto-columns: minmax(0, 1fr);
|
||||||
|
grid-auto-flow: column;
|
||||||
|
list-style: none;
|
||||||
|
//border: 1px solid black;
|
||||||
|
border-radius: 4px ;
|
||||||
|
transition: .5s;
|
||||||
|
li{
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
a,span{
|
||||||
|
height: $pnl-brc-height;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
i{
|
||||||
|
font-size: 25px;
|
||||||
|
margin: 0 .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: "";
|
||||||
|
width: 37px;
|
||||||
|
height: 37px;
|
||||||
|
border: 1px solid black;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
position: absolute;
|
||||||
|
inset-inline-start: -1.05rem;
|
||||||
|
background: #282d46;
|
||||||
|
top: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-child:before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
&:hover{
|
||||||
|
background: #444e7c;
|
||||||
|
background-image: url("../images/pattern.png");
|
||||||
|
|
||||||
|
& + li:before{
|
||||||
|
background: #444e7c;
|
||||||
|
background-image: url("../images/pattern.png");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
@if(session()->has('message'))
|
||||||
|
<div class="alert alert-info">
|
||||||
|
{{ session()->get('message') }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@foreach($errors->all() as $err)
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
{{$err}}
|
||||||
|
</div>
|
||||||
|
@endforeach
|
@ -1,3 +1,28 @@
|
|||||||
<nav id="panel-breadcrumb">
|
<nav id="panel-breadcrumb">
|
||||||
1
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="#1">
|
||||||
|
<i class="ri-home-3-line"></i>
|
||||||
|
{{config('app.name')}}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#2">
|
||||||
|
<i class="ri-dashboard-3-line"></i>
|
||||||
|
{{__("Dashboard")}}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#3">
|
||||||
|
<i class="ri-user-3-line"></i>
|
||||||
|
{{__("Users")}}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span>
|
||||||
|
<i class="ri-add-line"></i>
|
||||||
|
{{__("Add new user")}}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
Loading…
Reference in New Issue