Skip to content

Commit da2dda6

Browse files
authored
Merge pull request #16 from Libertech-FR/14-api-feature
14 api feature
2 parents 187bc35 + 4b75cc2 commit da2dda6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1688
-620
lines changed

Makefile

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,24 @@ help:
88
| sort | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[32m%-15s\033[0m %s\n", $$1, $$2}'
99

1010
dbs: ## Start databases
11-
@docker volume create $(APPNAME)-redis
12-
@docker run -d --rm \
13-
--name $(APPNAME)-redis \
14-
--network dev \
15-
-p 6379:6379 \
16-
redis
1711
@docker volume create $(APPNAME)-mongodb
1812
@docker run -d --rm \
1913
--name $(APPNAME)-mongodb \
2014
-v $(APPNAME)-mongodb:/data/db \
2115
-p 27017:27017 \
16+
-e MONGODB_REPLICA_SET_MODE=primary \
17+
-e MONGODB_REPLICA_SET_NAME=rs0 \
18+
-e ALLOW_EMPTY_PASSWORD=yes \
19+
--network dev \
20+
mongo:5.0 --replSet rs0 --wiredTigerCacheSizeGB 1.5 --quiet || true
21+
@docker volume create $(APPNAME)-redis
22+
@docker run -d --rm \
23+
--name $(APPNAME)-redis \
2224
--network dev \
23-
mongo:5.0 --wiredTigerCacheSizeGB 1.5 --quiet || true
25+
-p 6379:6379 \
26+
redis
27+
@sleep 1
28+
@docker exec -it teaket-mongodb mongo --eval "rs.initiate({_id: 'rs0', members: [{_id: 0, host: '127.0.0.1:27017'}]})" || true
2429

2530
stop-dbs: ## Stop databases
2631
@docker stop $(APPNAME)-redis || true
@@ -30,6 +35,4 @@ buildseeds: ## Build populate image
3035
docker build -t seeding -f ./populate/Dockerfile ./populate
3136

3237
populate-db: ## Populate database
33-
docker run --rm -it --network dev -v $(CURDIR)/populate:/app seeding
34-
35-
38+
docker run --rm --network dev -v ./populate:/app -v ./service/.dev-token.json:/app/.dev-token.json seeding

app/nuxt.config.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { extensions } from '@libertech-fr/teaket_common'
1+
import appSetup from './src/extension.app.setup'
22
import pugPlugin from 'vite-plugin-pug'
33
import openapiTS from 'openapi-typescript'
44
import { writeFileSync } from 'fs'
@@ -34,7 +34,7 @@ export default defineNuxtConfig({
3434
'nuxt-quasar-ui',
3535
'@vueuse/nuxt',
3636
'dayjs-nuxt',
37-
...extensions.appSetup.default(),
37+
...appSetup(),
3838
],
3939
auth: {
4040
globalMiddleware: true,
@@ -45,8 +45,7 @@ export default defineNuxtConfig({
4545
scheme: 'refresh',
4646
token: {
4747
property: 'access_token',
48-
maxAge: 1 * 5,
49-
// maxAge: 60 * 5,
48+
maxAge: 60 * 5,
5049
},
5150
refreshToken: {
5251
property: 'refresh_token',

app/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@
22
"name": "@libertech-fr/teaket_app",
33
"version": "0.0.1",
44
"scripts": {
5+
"prebuild": "nuxt prepare",
56
"build": "nuxt build",
7+
"predev": "nuxt prepare",
68
"dev": "nuxt dev",
79
"generate": "nuxt generate",
810
"preview": "nuxt preview",
9-
"postinstall": "nuxt prepare",
1011
"generate:api": "openapi-typescript http://localhost:7100/swagger/json -o ./.nuxt/types/service-api.d.ts",
1112
"upgrade:packages": "npx npm-check-updates -u --reject @types/node"
1213
},
1314
"dependencies": {
14-
"@libertech-fr/teaket_common": "*",
1515
"@nuxt-alt/auth": "^2.6.1",
1616
"@nuxt-alt/http": "^1.6.8",
1717
"@pinia/nuxt": "^0.4.11",

app/src/components/appbar/RightButtons.vue

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
<template lang='pug'>
22
div
3-
q-btn(v-for="button in buttons" :key="button.icon" round flat :icon="button.icon" size="lg")
4-
q-tooltip.text-body2(transition-show="scale" transition-hide="scale") {{ button.name }}
5-
q-btn-dropdown(icon="mdi-account-circle-outline" round flat size="lg")
6-
q-list
7-
q-item.q-pa-none(v-for="button in buttons" :key="button.name")
8-
q-btn.full-width.items-baseline.q-pa-sm(
9-
:icon="button.icon"
10-
:label="button.name"
11-
:color="button?.color || 'primary'"
12-
@click="button?.action"
13-
:to='button?.to'
14-
flat
15-
dense
16-
)
3+
q-btn(v-for="button in buttons" :key="button.icon" round flat :icon="button.icon" size="md").q-mx-sm
4+
q-tooltip.text-body2(transition-show="scale" transition-hide="scale") {{ button.name }}
5+
q-btn-dropdown(icon="mdi-account-circle-outline" round flat size="md")
6+
q-list
7+
q-item.q-pa-none(v-for="button in buttons" :key="button.name")
8+
q-btn.full-width.items-baseline.q-pa-sm(
9+
:icon="button.icon"
10+
:label="button.name"
11+
:color="button?.color || 'primary'"
12+
@click="button?.action"
13+
:to='button?.to'
14+
flat
15+
dense
16+
)
1717
</template>
1818

1919
<script lang='ts' setup>
Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
<template lang="pug">
2-
q-toolbar.bg-primary.text-white
3-
q-btn(flat)
4-
q-avatar
5-
img(src="/logo.svg")
6-
q-toolbar-title Teaket
7-
q-separator(vertical dark inset).q-mx-md
8-
tk-AppbarMenu
9-
q-space
10-
tk-AppbarSearchfield
11-
q-separator(vertical dark inset).q-mx-md
12-
tk-AppbarRightButtons
2+
q-header
3+
q-toolbar.bg-primary.text-white(style={height: '32px'})
4+
q-btn(flat)
5+
q-avatar
6+
img(src="/logo.svg")
7+
q-toolbar-title Teaket
8+
q-separator(vertical dark inset).q-mx-md
9+
tk-AppbarMenu
10+
q-space
11+
tk-AppbarSearchfield
12+
q-separator(vertical dark inset).q-mx-md
13+
tk-AppbarRightButtons
1314
</template>
1415

1516
<script lang="ts" setup>
16-
</script>
17+
</script>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<template lang="pug">
2+
q-footer
3+
q-toolbar.bg-primary.text-white
4+
5+
</template>
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
<template lang="pug">
2+
.row.q-gutter-sm.items-center.q-mt-sm
3+
//- .col.col-md-2
4+
//- q-select(:options="fieldTypes" label="Type de champ" v-model="fieldType" clearable @update:model-value="clearFields(['field', 'comparator'])")
5+
.col.col-md-2
6+
q-select(:options="fields" label="Champs" v-model="field" clearable @update:model-value="onFieldChange($event)")
7+
.col.col-md-2
8+
q-select(:options="comparatorFilteredByType" label="Comparateurs" v-model="comparator" clearable @update:model-value="clearFields([])" :disable="isFieldDisabled.comparator")
9+
template(v-slot:selected-item="scope")
10+
q-icon(:name="scope.opt.icon" size="xs")
11+
template(v-slot:option="scope")
12+
q-item(v-bind="scope.itemProps")
13+
q-item-section(avatar)
14+
q-icon(:name="scope.opt.icon")
15+
q-item-section
16+
q-item-label
17+
span {{ scope.opt.label }}
18+
19+
.col-12.col-md-2(v-show="!comparator?.multiplefields")
20+
q-input(v-model="search" label="Rechercher" clearable :type="searchInputType" :disable="isFieldDisabled.search" :prefix="comparator?.prefix" :suffix="comparator?.suffix")
21+
.col-6.col-md-2(v-show="comparator?.multiplefields")
22+
q-input(v-model="searchMin" label="Min" clearable :type="searchInputType" :disable="isFieldDisabled.search" )
23+
.col-6.col-md-2(v-show="comparator?.multiplefields")
24+
q-input(v-model="searchMax" label="Max" clearable :type="searchInputType" :disable="isFieldDisabled.search" )
25+
.col-12.col-md-1
26+
q-btn(color="primary" @click="addFilter" :disable="isFieldDisabled.addButton") Ajouter
27+
q-space
28+
.col-12.col-md-2
29+
tk-SearchfiltersRightSelect(ref="rightSelect")
30+
</template>
31+
32+
<script lang="ts" setup>
33+
import { ref, computed, inject } from 'vue'
34+
import { useRouter, useRoute } from 'nuxt/app'
35+
import { useDayjs } from '#imports';
36+
import type { Filter, Field, Comparator, SearchFilter } from '~/types'
37+
const dayjs = useDayjs()
38+
39+
const fields = inject('fieldsList', ref<Field[]>([]))
40+
41+
const router = useRouter()
42+
const route = useRoute()
43+
const rightSelect = ref(null)
44+
const inclus = ref(true)
45+
const fieldType = ref<string>()
46+
47+
let field = ref<Field>()
48+
let comparator = ref<Comparator>()
49+
const search = ref('')
50+
const searchMin = ref('')
51+
const searchMax = ref('')
52+
53+
54+
const filters = ref<Filter[]>([])
55+
56+
const fieldTypes = ref<{
57+
label: string
58+
value: string
59+
}[]>([
60+
{ label: 'Texte', value: 'text' },
61+
{ label: 'Nombre', value: 'number' },
62+
{ label: 'Date', value: 'date' },
63+
])
64+
65+
const comparatorTypes = ref<Comparator[]>([
66+
{ label: 'Egal à', querySign: '=', value: '=', icon: 'mdi-equal', type: ['number'], multiplefields: false, prefix: '', suffix: '' },
67+
{ label: 'Différent', querySign: '!=', value: '!=', icon: 'mdi-exclamation', type: ['number'], multiplefields: false, prefix: '', suffix: '' },
68+
{ label: 'Supérieur à', querySign: '>', value: '>', icon: 'mdi-greater-than', type: ['number', 'date'], multiplefields: false, prefix: '', suffix: '' },
69+
{ label: 'Supérieur ou égal à', querySign: '>=', value: '>=', icon: 'mdi-greater-than-or-equal', type: ['number', 'date'], multiplefields: false, prefix: '', suffix: '' },
70+
{ label: 'Inférieur à', querySign: '<', value: '<', icon: 'mdi-less-than', type: ['number', 'date'], multiplefields: false, prefix: '', suffix: '' },
71+
{ label: 'Inférieur ou égal à', querySign: '<=', value: '<=', icon: 'mdi-less-than-or-equal', type: ['number', 'date'], multiplefields: false, prefix: '', suffix: '' },
72+
{ label: 'entre', querySign: '<<', value: 'between', icon: 'mdi-arrow-expand-horizontal', type: ['number', 'date'], multiplefields: true, prefix: '', suffix: '' },
73+
{ label: 'Contiens', querySign: '^', value: '^', icon: 'mdi-apple-keyboard-control', type: ['text'], multiplefields: false, prefix: '/', suffix: '/' },
74+
{ label: 'Commence par', querySign: '^', value: '/^', icon: 'mdi-apple-keyboard-control', type: ['text'], multiplefields: false, prefix: '/^', suffix: '/' },
75+
{ label: 'Fini par', querySign: '^', value: '$/', icon: 'mdi-apple-keyboard-control', type: ['text'], multiplefields: false, prefix: '/', suffix: '$/' },
76+
{ label: 'Egal à', querySign: '@', value: '@', icon: 'mdi-apple-keyboard-control', type: [], multiplefields: true, prefix: '', suffix: '' },
77+
78+
])
79+
80+
const onFieldChange = (value: Field) => {
81+
value === null ? fieldType.value = '' : fieldType.value = value.type
82+
clearFields(['comparator'])
83+
}
84+
85+
const clearFields = (fields: string[]) => {
86+
if (fields.includes('field')) field = ref()
87+
if (fields.includes('comparator')) comparator = ref()
88+
search.value = ''
89+
searchMin.value = ''
90+
searchMax.value = ''
91+
}
92+
93+
const addFilter = async () => {
94+
const searchFilter = getSearchFilter.value
95+
if (!searchFilter) return
96+
if (searchFilter.comparator.multiplefields) {
97+
for (const { key, value } of parseMultipleFilter(searchFilter)) {
98+
await pushQuery(key, value)
99+
}
100+
} else {
101+
const { key, value } = parseSimpleFilter(searchFilter)
102+
await pushQuery(key, value)
103+
}
104+
}
105+
106+
const parseSimpleFilter = (searchFilter: SearchFilter) => {
107+
if (searchFilter.field.type === 'date') {
108+
if (searchFilter.comparator.querySign === '<' || searchFilter.comparator.querySign === '>=') searchFilter.search = dayjs(searchFilter.search).startOf('day').toISOString()
109+
if (searchFilter.comparator.querySign === '>' || searchFilter.comparator.querySign === '<=') searchFilter.search = dayjs(searchFilter.search).endOf('day').toISOString()
110+
}
111+
return {
112+
key: `filters[${searchFilter.comparator.querySign}${searchFilter.field.name}]`,
113+
value: `${searchFilter.comparator.prefix}${searchFilter.search}${searchFilter.comparator.suffix}`
114+
}
115+
}
116+
117+
const parseMultipleFilter = (searchFilter: SearchFilter) => {
118+
if (searchFilter.field.type === 'date') {
119+
searchFilter.searchMin = dayjs(searchFilter.searchMin).startOf('day').toISOString()
120+
searchFilter.searchMax = dayjs(searchFilter.searchMax).endOf('day').toISOString()
121+
}
122+
const min = {
123+
key: `filters[>=${searchFilter.field.name}]`,
124+
value: `${searchFilter.comparator.prefix}${searchFilter.searchMin}${searchFilter.comparator.suffix}`
125+
}
126+
const max = {
127+
key: `filters[<=${searchFilter.field.name}]`,
128+
value: `${searchFilter.comparator.prefix}${searchFilter.searchMax}${searchFilter.comparator.suffix}`
129+
}
130+
return [min, max]
131+
}
132+
133+
const pushQuery = async (key: string, value: string) => {
134+
const query = {
135+
...route.query,
136+
}
137+
query[key] = value
138+
await router.push({
139+
query
140+
})
141+
}
142+
143+
const getSearchFilter = computed(() => {
144+
if (field.value === undefined || field.value === null) return null
145+
if (comparator.value === undefined || comparator.value === null) return null
146+
return {
147+
field: field!.value,
148+
comparator: comparator!.value,
149+
search: search.value,
150+
searchMin: searchMin.value,
151+
searchMax: searchMax.value
152+
}
153+
})
154+
155+
const searchInputType = computed(() => {
156+
if (fieldType.value === undefined || fieldType.value === null) return 'text'
157+
return fieldType.value
158+
})
159+
160+
const fieldsFilteredByType = computed(() => {
161+
if (fieldType.value === undefined || fieldType.value === null) return []
162+
return fields.value.filter((field) => {
163+
return field.type === fieldType.value
164+
})
165+
})
166+
167+
const comparatorFilteredByType = computed(() => {
168+
if (fieldType.value === undefined || fieldType.value === null) return []
169+
return comparatorTypes.value.filter((comparator) => {
170+
return comparator.type.includes(fieldType.value!)
171+
})
172+
})
173+
174+
const isFieldDisabled = computed(() => {
175+
return {
176+
field: false,
177+
comparator: !field.value,
178+
search: !field.value || !comparator.value,
179+
addButton: !field.value || !comparator.value || !search.value,
180+
}
181+
})
182+
183+
defineExpose({
184+
comparatorTypes,
185+
rightSelect,
186+
})
187+
</script>

0 commit comments

Comments
 (0)