First commit

This commit is contained in:
2024-06-06 22:48:20 +02:00
commit 2a25500946
104 changed files with 17186 additions and 0 deletions

View File

@@ -0,0 +1,41 @@
<template>
<q-item clickable tag="a" target="_blank" :href="link">
<q-item-section v-if="icon" avatar>
<q-icon :name="icon" />
</q-item-section>
<q-item-section>
<q-item-label>{{ title }}</q-item-label>
<q-item-label caption>{{ caption }}</q-item-label>
</q-item-section>
</q-item>
</template>
<script>
import { defineComponent } from "vue";
export default defineComponent({
name: "EssentialLink",
props: {
title: {
type: String,
required: true,
},
caption: {
type: String,
default: "",
},
link: {
type: String,
default: "#",
},
icon: {
type: String,
default: "",
},
},
});
</script>

View File

@@ -0,0 +1,50 @@
<script setup>
import Multiselect from 'vue-multiselect';
import JsonQueryBuilderGroup from '@/Components/JsonQueryBuilderGroup.vue';
import { FolderAddIcon, PlusCircleIcon, TrashIcon, ArrowCircleRightIcon} from '@vue-hero-icons/outline';
import VueTailwindDatepicker from 'vue-tailwind-datepicker'
import { ref } from 'vue';
import { computed } from 'vue';
defineProps(['query', 'currentQuery', 'queryOptions', 'i18n']);
defineEmits(['update:query', 'update:queryOptions', 'runQuery']);
const level = ref(0);
const expanded = ref(true);
</script>
<template>
<div class="q-pa-md row items-start q-gutter-md">
<!-- Card -->
<q-card class="my-card q-pa-sm">
<!-- Header -->
<q-card-section class="bg-red-4 text-white">
<div class="row items-center">
<div class="col text-h6">Query Builder</div>
<q-card-actions align="right" class="col">
<q-btn
color="grey"
round
flat
dense
:icon="expanded ? 'keyboard_arrow_up' : 'keyboard_arrow_down'"
@click="expanded = !expanded"
/>
</q-card-actions>
</div>
</q-card-section>
<q-slide-transition>
<div v-show="expanded">
<q-separator />
<JsonQueryBuilderGroup :currentQuery="query" :queryOptions="queryOptions" :level="level+1"> </JsonQueryBuilderGroup>
</div>
</q-slide-transition>
<div class="row no-wrap justify-end q-mt-md q-pa-sm bg-grey-8 text-white">
<q-btn rounded color="brown-4" @click="$emit('runQuery')" label="Spusti vyhľadávanie" icon="sync" />
</div>
</q-card>
</div>
</template>

View File

@@ -0,0 +1,86 @@
<script setup>
import { FolderAddIcon, PlusCircleIcon, TrashIcon} from '@vue-hero-icons/outline';
import JsonQueryBuilderRule from '@/Components/JsonQueryBuilderRule.vue';
import { ref, onMounted } from 'vue';
import { computed } from 'vue';
const props = defineProps(['currentQuery', 'queryOptions', 'i18n', 'level']);
function AddGroup(query) {
console.log(query.rules);
let len = query.rules.push({
condition: 'and',
rules: []
});
AddRule(query.rules[len-1]);
}
function DeleteGroup(query,item) {
console.log(item);
query.rules.splice(query.rules.indexOf(item), 1);
}
function AddRule(query){
console.log(query.rules);
query.rules.push({
id: '',
operator: '=',
value: ''
})
}
function DeleteRule(rules, rule) {
console.log(rule);
rules.splice(rules.indexOf(rule), 1);
}
</script>
<template>
<div :class="level > 1 ? 'border-4 border-blue-300 q-pa-xs' : 'q-pa-xs'">
<div class="row q-pa-sm q-gutter-sm items-center justify-between">
<div class="p-3 text-gray-700 text-lg font-bold">
<q-btn-toggle
v-model="currentQuery.condition"
class="q-"
no-caps
rounded
unelevated
toggle-color="blue"
color="white"
text-color="primary"
:options="[
{label: 'AND', value: 'and'},
{label: 'OR', value: 'or'}
]"
/>
</div>
<div class="p-3 text-gray-700 text-md font-bold">
<q-btn-group push>
<q-btn v-if="level > 1" @click="$emit('deleteGroup')" color="yellow" glossy text-color="black" push label="Zmaž skupinu" icon="delete" />
<q-btn color="amber" @click="AddGroup(currentQuery)" glossy text-color="black" push label="Pridaj skupinu" icon="folder"/>
<q-btn color="orange" @click="AddRule(currentQuery)" glossy text-color="black" push label="Pridaj pravidlo" icon="add" />
</q-btn-group>
</div>
</div>
<div class="q-pa-xs" v-for="(item) in currentQuery.rules" :key="item._uuid">
<JsonQueryBuilderGroup v-if="typeof item.condition === 'string'" :currentQuery="item" :queryOptions="queryOptions" @delete-group="DeleteGroup(currentQuery,item)" :level="level + 1"></JsonQueryBuilderGroup>
<JsonQueryBuilderRule v-else :rule="item" :options="queryOptions" @delete-rule="DeleteRule(currentQuery.rules,item)"></JsonQueryBuilderRule>
</div>
</div>
</template>
<style>
.q-btn-group {
border: 1px solid gray;
}
.border-4 {
border: 4px solid blue;
}
</style>

View File

@@ -0,0 +1,166 @@
<script setup>
import Multiselect from 'vue-multiselect';
import { matDelete } from '@quasar/extras/material-icons';
import { ref, onMounted } from 'vue';
import { computed } from 'vue';
const props = defineProps(['rule', 'options', 'i18n']);
console.log("VALUE",props.rule.value);
const dateValue = ref(props.rule.value);
const filteredOptions = ref(props.options);
const formatter = ref({
date: 'D.M.YYYY',
month: 'MMM'
});
const fieldValue = ref(selectByValue(props.options,"name",props.rule.id));
const rulesOperatorOptions = computed({
get () {
switch(fieldValue.value.type) {
case 'Number':
return [
{id: '=', name: 'rovná sa'},
{id: '!=', name: 'nerovná sa'},
{id: '>', name: 'je väčšie'},
{id: '<', name: 'je menšie'},
{id: '>=', name: 'je rovné, vačšie'},
{id: '<=', name: 'je menšie, rovné'}
];
case 'Date':
return [
{id: '=', name: 'rovná sa'},
{id: '!=', name: 'nerovný'},
{id: '>', name: 'je väčší'},
{id: '<', name: 'je menší'},
{id: '>=', name: 'je rovný, vačší'},
{id: '<=', name: 'je menši, rovný'}
];
case 'String':
default:
return [
{id: '=', name: 'rovná'},
{id: '!=', name: 'nerovná sa'},
{id: 'in', name: 'je v'},
{id: 'not in', name: 'nieje v'},
{id: '()', name: 'obsahuje'},
{id: '!()', name: 'neobsahuje'},
{id: '(', name: 'začína'},
{id: ')', name: 'končí'},
{id: '!(', name: 'nezačína'},
{id: '!)', name: 'nekončí'}
];
}
},
}
);
const criteria = ref(selectByValue(rulesOperatorOptions.value,"id",props.rule.operator));
const input = ref(props.rule.value);
function filterFn (val, update, abort) {
update(() => {
const needle = val.toLocaleLowerCase();
filteredOptions.value = props.options.filter(v => v.desc.toLocaleLowerCase().indexOf(needle) > -1)
})
}
function setModel (val) {
fieldValue.value.value = val
}
function selectByValue(arr, id, value, single = true) {
let filtered = arr.filter(a => a[id] == value);
console.log(filtered);
if (single && filtered.length > 0)
return filtered[0];
return filtered;
}
function FieldChange(val) {
console.log(val);
props.rule.id = val.name;
fieldValue.value.type = val.type;
input.value = null;
props.rule.value = null;
}
function CriteriaChange(params) {
console.log("PARAMS=",params);
props.rule.operator = params.id;
}
function DateChange(params) {
console.log(params);
props.rule.value = params[0];
}
function InputChange(params) {
console.log(params);
props.rule.value = params;
}
</script>
<template>
<div class="row qa-pa-xs q-gutter-sm justify-between ">
<div class="">
<q-select
dense
filled
use-input
hide-selected
fill-input
v-model="fieldValue"
option-label="desc"
option-value="id"
:options="filteredOptions"
@input-value="setModel"
@filter="filterFn"
style="min-width: 400px;"
/>
</div>
<div class="col">
<q-select
dense
filled
fill-input
v-model="criteria"
option-label="name"
option-value="name"
@update:model-value="CriteriaChange"
:options="rulesOperatorOptions"
/>
</div>
<div class="col">
<q-input v-if="fieldValue.type == 'Date'" dense filled v-model="dateValue" mask="date" >
<template v-slot:append>
<q-icon name="event" class="cursor-pointer">
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
<q-date v-model="dateValue">
<div class="row items-center justify-end">
<q-btn v-close-popup label="Close" color="primary" flat />
</div>
</q-date>
</q-popup-proxy>
</q-icon>
</template>
</q-input>
<q-input v-if="fieldValue.type == 'String'" id="value" outlined dense filled type="text" class="block w-full" v-model="input" required @update:model-value="InputChange" />
<q-input v-if="fieldValue.type == 'Number'" id="value" outlined dense filled type="number" class="block w-full" v-model="input" required @update:model-value="InputChange" />
</div>
<div>
<q-btn rounded color="red" text-color="black" :icon="matDelete" @click="$emit('deleteRule')" />
</div>
</div>
</template>
<style>
@import 'vue-multiselect/dist/vue-multiselect.css';
</style>

View File

@@ -0,0 +1,28 @@
<script setup>
import { Link } from '@inertiajs/vue3';
defineProps({
links: {
type: Object,
default: () => ({}),
},
data: {
type: Object,
default: () => ({}),
}
});
</script>
<template>
<div v-if="links.length > 3">
<div class="flex flex-wrap -mb-1">
<template v-for="(link, p) in links" :key="p">
<div v-if="link.url === null" class="mr-1 mb-1 px-4 py-3 text-sm leading-4 text-gray-400 border rounded"
v-html="link.label" />
<Link v-else
class="mr-1 mb-1 px-4 py-3 text-sm leading-4 border rounded hover:bg-white focus:border-indigo-500 focus:text-indigo-500"
:class="{ 'bg-blue-700 text-white': link.active }" method="post" :data="data" :href="link.url" v-html="link.label" as="button" />
</template>
</div>
</div>
</template>

View File

@@ -0,0 +1,60 @@
<script setup>
import { Link } from '@inertiajs/inertia-vue3';
defineProps({
links: {
type: Object,
default: () => ({}),
},
data: {
type: Object,
default: () => ({}),
}
});
</script>
<template>
<ul v-if="links.length > 3" class="inline-flex -space-x-px">
<template v-for="(link, p) in links" :key="p">
<li v-if="link.url === null" class="bg-white border border-gray-300 text-gray-500 hover:bg-gray-100 hover:text-gray-700 ml-0 rounded-l-lg leading-tight py-2 px-3 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white" v-html=""/>
<a href="#"
class="bg-white border border-gray-300 text-gray-500 hover:bg-gray-100 hover:text-gray-700 leading-tight py-2 px-3 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white">1</a>
</li>
<li>
<a href="#"
class="bg-white border border-gray-300 text-gray-500 hover:bg-gray-100 hover:text-gray-700 leading-tight py-2 px-3 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white">2</a>
</li>
<li>
<a href="#" aria-current="page"
class="bg-blue-50 border border-gray-300 text-blue-600 hover:bg-blue-100 hover:text-blue-700 py-2 px-3 dark:border-gray-700 dark:bg-gray-700 dark:text-white">3</a>
</li>
<li>
<a href="#"
class="bg-white border border-gray-300 text-gray-500 hover:bg-gray-100 hover:text-gray-700 leading-tight py-2 px-3 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white">4</a>
</li>
<li>
<a href="#"
class="bg-white border border-gray-300 text-gray-500 hover:bg-gray-100 hover:text-gray-700 leading-tight py-2 px-3 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white">5</a>
</li>
<li>
<a href="#"
class="bg-white border border-gray-300 text-gray-500 hover:bg-gray-100 hover:text-gray-700 rounded-r-lg leading-tight py-2 px-3 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white">Next</a>
</li>
</template>
</ul>
<div v-if="links.length > 3">
<div class="flex flex-wrap -mb-1">
<template v-for="(link, p) in links" :key="p">
<div v-if="link.url === null" class="mr-1 mb-1 px-4 py-3 text-sm leading-4 text-gray-400 border rounded"
v-html="link.label" />
<Link v-else
class="mr-1 mb-1 px-4 py-3 text-sm leading-4 border rounded hover:bg-white focus:border-indigo-500 focus:text-indigo-500"
:class="{ 'bg-blue-700 text-white': link.active }" method="post" :data="data" :href="link.url" v-html="link.label" as="button" />
</template>
</div>
</div>
</template>

View File

@@ -0,0 +1,118 @@
<template>
<div class="table-main">
<div
v-for="(row, index) in arrData"
:key="index"
class="row-data m-2 d-flex"
>
<div class="key p-2 d-inline-block">
<div class="text-capitalize ">
{{ printRpoName(row) }}
</div>
</div>
<div v-if="['string', 'number'].includes(checkValueType(data[row]))">
<div class="value p-2 d-inline-block">{{ data[row] }}</div>
</div>
<div v-else-if="checkValueType(data[row]) === 'array'">
<div v-for="(arrRow, index2) in data[row]" :key="index2" class="d-flex">
<div v-if="['string', 'number'].includes(checkValueType(arrRow))">
{{ arrRow }}
</div>
<div v-else>
<RpoRecord :data="arrRow" :rpo="subRpo(arrRow, row,index2)"/>
</div>
</div>
</div>
<div v-else>
<RpoRecord :data="data[row]" :rpo="subRpo(data[row],row,index)" />
</div>
</div>
</div>
</template>
<script>
import rpoJsonData from '../Data/RpoTree.json';
export default {
name: "RpoRecord",
props: {
data: {
type: Object
},
rpo: {
type: Object,
}
},
computed: {
arrData() {
return Object.keys(this.data);
}
},
methods: {
keyTitle(key) {
return key.split("_").join(" ");
},
checkValueType(val) {
if (typeof val !== "object") {
return typeof val;
}
return Array.isArray(val) ? "array" : "object";
},
printRpoName(key) {
console.log('data',typeof this.rpo[key]);
if (typeof this.rpo[key] !== 'undefined'){
console.log(this.rpo[key]);
return this.rpo[key]["desc"];
}
if (typeof this.rpo[key] !== 'undefined' && typeof this.rpo[key]["value"] !== 'undefined') {
console.log(this.rpo[key]);
return this.rpo[key].value.desc;
}
return key;
},
subRpo(val,kz,i) {
console.log('val=',val,'k=',kz,'i=',i);
console.log(this.rpo);
return this.rpo[kz];
}
},
mounted() {
console.log('JSON=',this.rpo);
}
};
</script>
<style scoped>
.table-main {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.m-2 {
margin: .5rem!important;
}
.mx-2 {
margin-right: .5rem!important;
}
.p-2 {
padding: .5rem!important;
}
.d-flex {
display: flex!important;
}
.d-inline-block {
display: inline-block!important;
}
.text-capitalize {
text-transform: capitalize!important;
}
.key {
background: lightgray;
}
.table-main .row-data {
border: 2px solid grey;
border-radius: 2px;
}
</style>

View File

@@ -0,0 +1,204 @@
<script setup>
import rpoJsonData from '../Data/RpoTree.json';
import { ref, onMounted } from 'vue';
import { computed } from 'vue';
const props = defineProps({
data: {
type: Object,
required: true
},
rpo: {
type: Object,
required: false,
default: rpoJsonData
}
});
const columns = [
{
name: 'translated',
required: true,
label: 'Nazov',
align: 'left',
field: 'translated',
sortable: false,
},
{ name: 'val', align: 'left', label: 'Hodnota', field: 'val', sortable: false },
]
const arrData = computed(() => Object.keys(props.data));
const title = ref(props.data["fullNames"][0]["value"]);
console.log('TITLE', title);
function keyTitle(key) {
return key.split("_").join(" ");
}
function checkValueType(val) {
if (typeof val !== "object") {
return typeof val;
}
return Array.isArray(val) ? "array" : "object";
}
function printRpoName(rpoKey) {
console.log('data', typeof props.rpo[rpoKey]);
console.log('rpoKey', rpoKey);
if (typeof props.rpo[rpoKey] !== 'undefined') {
if (typeof props.rpo[rpoKey]["desc"] !== 'undefined') {
console.log(props.rpo[rpoKey]);
return props.rpo[rpoKey].desc;
}
if (typeof props.rpo[rpoKey]["value"] !== 'undefined') {
console.log(props.rpo[rpoKey]);
return props.rpo[rpoKey].value.desc;
}
console.log(props.rpo[rpoKey]);
return props.rpo[rpoKey]["desc"];
}
return rpoKey;
}
function createRPOflatList(resultTree, flatList, RpoTree, lastKey = undefined, level = 0, fullPathRpo = []) {
console.log('RESTREE=', resultTree);
let fullPath = [...fullPathRpo];
if (lastKey != undefined) fullPath.push(lastKey);
if (level == 0 || checkValueType(resultTree) == "object") {
let i = 0;
Object.keys(resultTree).forEach(k => {
if (typeof resultTree[k] != 'string') {
if (RpoTree[k] !== undefined) {
flatList.push({ "level": level, "key": k, "val": undefined, "translated": RpoTree[k].desc, "full": fullPath.join(' => '), "index": i++ });
createRPOflatList(resultTree[k], flatList, RpoTree[k], k, level + 1, fullPath);
}
} else {
console.log('RT=', RpoTree);
console.log('K=', k);
if (RpoTree[k] !== undefined)
flatList.push({ "level": level, "key": k, "val": resultTree[k], "translated": RpoTree[k].desc, "full": fullPath.join(' => '), "index": i++ });
}
});
} else if (checkValueType(resultTree) == "array") {
let i = 0;
for (const e of resultTree) {
console.log('e=', e);
if (checkValueType(e) == "object" || checkValueType(e) == "array") {
createRPOflatList(e, flatList, RpoTree, lastKey, level + 1, fullPath);
} else {
console.log('RT=', RpoTree);
flatList.push({ "level": level, "key": lastKey, "val": e, "translated": RpoTree.desc !== undefined ? RpoTree.desc : '', "full": fullPath.join(' => '), "index": i++ });
}
}
}
}
function subRpo(val, kz, i) {
console.log('val=', val, 'k=', kz, 'i=', i);
console.log(props.rpo);
return props.rpo[kz];
}
var flatListRpo = [];
createRPOflatList(props.data, flatListRpo, props.rpo);
console.log('FLAT=', flatListRpo);
</script>
<template>
<!-- https://github.com/quasarframework/quasar/blob/dev/docs/src/examples/QTable/VirtscrollExpandedRow.vue -->
<div class="col q-pa-md" style="overflow: auto;">
<q-table style="height: 700px;" separator="cell" flat bordered dense :rows-per-page-options="[0]"
:title="title" :rows="flatListRpo" :columns="columns" wrap-cells card-class="bg-deep-purple-7 text-white"
table-header-class="bg-deep-purple-1 text-black" row-key="name">
<template v-slot:body="props">
<q-tr :props="props" :key="`e_${props.row.index}`" class="q-virtual-scroll--with-prev">
<q-td class="bg-grey-8">
<div :class="`text-left level-${props.row.level}`">{{ props.row.translated }}</div>
</q-td>
<q-td class="bg-grey-6">
<div class="text-left">{{ props.row.val }}</div>
</q-td>
</q-tr>
</template>
</q-table>
</div>
</template>
<style scoped>
.table-main {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.level-1 {
padding-left: 10px;
}
.level-2 {
padding-left: 20px;
}
.level-3 {
padding-left: 30px;
}
.level-4 {
padding-left: 40px;
}
.level-5 {
padding-left: 50px;
}
.m-2 {
margin: 0rem !important;
}
.mx-2 {
margin-right: .5rem !important;
}
.p-2 {
padding: .5rem !important;
}
.d-flex {
display: flex !important;
}
.d-inline-block {
display: inline-block !important;
}
.text-capitalize {
text-transform: capitalize !important;
}
.key {
background: lightgray;
box-shadow:
2px 0 0 0 #888,
0 2px 0 0 #888,
2px 2px 0 0 #888,
/* Just to fix the corner */
2px 0 0 0 #888 inset,
0 2px 0 0 #888 inset;
}
.table-main .row-data {
box-shadow:
2px 0 0 0 #000,
0 2px 0 0 #000,
2px 2px 0 0 #000,
/* Just to fix the corner */
2px 0 0 0 #000 inset,
0 2px 0 0 #000 inset;
}
</style>