Skip to content

Commit 8b71d90

Browse files
committed
Merge branch 'app-developement' of https://github.com/Libertech-FR/teaket into app-developement
2 parents 6bc4b0c + 07737ff commit 8b71d90

File tree

24 files changed

+565
-179
lines changed

24 files changed

+565
-179
lines changed

Makefile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,17 @@ help:
99
@grep -E '^[-a-zA-Z0-9_\.\/]+:.*?## .*$$' $(MAKEFILE_LIST) \
1010
| sort | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[32m%-15s\033[0m %s\n", $$1, $$2}'
1111

12+
run-app:
13+
cd ./app && yarn dev
14+
15+
run-service:
16+
cd ./service && yarn start:dev
17+
18+
run: ## Run app and service
19+
@make dbs &
20+
@make run-app &
21+
@make run-service &
22+
1223
dbs: ## Start databases
1324
@docker volume create $(APPNAME)-mongodb
1425
@docker run -d --rm \

app/nuxt.config.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import pugPlugin from 'vite-plugin-pug'
33
import openapiTS from 'openapi-typescript'
44
import { writeFileSync } from 'fs'
55
import { resolve } from 'path'
6+
import timezone from 'dayjs/plugin/timezone'
67

78
const TK_APP_API_URL = process.env.TK_APP_API_URL || 'http://localhost:7100'
89

@@ -73,6 +74,17 @@ export default defineNuxtConfig({
7374
},
7475
},
7576
},
77+
dayjs: {
78+
locales: ['fr', 'en'],
79+
defaultLocale: 'fr',
80+
defaultTimezone: 'Paris',
81+
plugins: ['timezone', 'relativeTime']
82+
},
83+
pinia:{
84+
autoImports: [
85+
'defineStore'
86+
]
87+
},
7688
appConfig: {
7789
customSlots: {},
7890
},

app/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,19 @@
1717
"@pinia/nuxt": "^0.4.11",
1818
"@quasar/extras": "^1.16.6",
1919
"@vueuse/router": "^10.4.1",
20+
"bson-objectid": "^2.0.4",
2021
"cookie": "^0.5.0",
2122
"pinia": "^2.1.6",
2223
"quasar": "^2.12.6"
2324
},
2425
"devDependencies": {
2526
"@nuxt/devtools": "latest",
2627
"@types/node": "^18.17.3",
28+
"@typescript-eslint/eslint-plugin": "^6.6.0",
29+
"@typescript-eslint/parser": "^6.6.0",
2730
"@vueuse/core": "^10.4.1",
2831
"@vueuse/nuxt": "^10.4.1",
2932
"dayjs-nuxt": "^1.1.2",
30-
"@typescript-eslint/eslint-plugin": "^6.6.0",
31-
"@typescript-eslint/parser": "^6.6.0",
3233
"eslint": "^8.49.0",
3334
"nuxt": "~3.6.5",
3435
"nuxt-quasar-ui": "^2.0.5",

app/src/components/searchfilters/RightSelect.vue

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,16 @@ q-select(
1313
@add="addFilter($event)"
1414
@remove="removeFilter($event)"
1515
)
16-
template(v-slot:option="{ index, itemProps, opt, selected, toggleOption }")
17-
q-item-label(v-bind="itemProps" v-if="opt.header" header) {{ opt.label }}
18-
//@click.capture='addFilter({index, value: opt})'
19-
q-item(v-bind="itemProps" v-else)
20-
q-item-section(side)
21-
q-icon(:name="opt.icon" :color="opt.color" size="xs")
22-
q-item-section
23-
q-item-label(v-html="opt.label")
24-
q-item-section(side)
25-
q-toggle(:model-value="selected" @update:model-value="toggleOption")
16+
template(v-slot:option="{ index, itemProps, opt, selected, toggleOption }")
17+
q-item-label(v-bind="itemProps" v-if="opt.header" header) {{ opt.label }}
18+
//@click.capture='addFilter({index, value: opt})'
19+
q-item(v-bind="itemProps" v-else)
20+
q-item-section(side)
21+
q-icon(:name="opt.icon" :color="opt.color" size="xs")
22+
q-item-section
23+
q-item-label(v-html="opt.label")
24+
q-item-section(side)
25+
q-toggle(:model-value="selected" @update:model-value="toggleOption")
2626

2727
</template>
2828

@@ -32,7 +32,7 @@ import type { Ref } from 'vue'
3232
import { useHttpApi } from "~/composables/useHttpApi";
3333
import type { components } from '#build/types/service-api'
3434
import { useRoute, useRouter } from 'nuxt/app';
35-
35+
import { ticketType, lifeSteps } from "#imports";
3636
type Category = components['schemas']['CategoriesDto']
3737
type State = components['schemas']['StatesDto']
3838
@@ -49,12 +49,7 @@ const router = useRouter()
4949
5050
const { data: statesData } = inject('stateFetch')
5151
const { data: categoriesData } = inject('categoriesFetch')
52-
const ticketType: {
53-
label: string,
54-
value: number,
55-
icon: string,
56-
color: string
57-
}[] | undefined = inject('ticketType')
52+
5853
onMounted(() => {
5954
getFilters()
6055
})
@@ -88,12 +83,6 @@ const getFilters = () => {
8883
8984
9085
const filters = ref<Option[]>([])
91-
const lifeSteps = ref<Option[]>([
92-
{ label: 'Lifestep', header: true },
93-
{ label: 'Ouvert', value: '1', group: 'lifestep', icon: 'mdi-circle', color: "green" },
94-
{ label: 'Clos', value: '0', group: 'lifestep', icon: 'mdi-circle', color: "red" },
95-
])
96-
9786
const options = computed(() => {
9887
// const categories: Option[] = categoriesData.value.data.map((category: Category) => {
9988
// return {
@@ -103,7 +92,7 @@ const options = computed(() => {
10392
// }
10493
// }) ?? []
10594
// categories.unshift({ label: 'Catégories', header: true })
106-
const ticketTypeOptions: Option[] = ticketType.value.map((type) => {
95+
const ticketTypeOptions: Option[] = ticketType.map((type) => {
10796
return {
10897
label: type.label,
10998
value: type.value.toString(),
@@ -123,8 +112,9 @@ const options = computed(() => {
123112
}
124113
}) ?? []
125114
states.unshift({ label: 'États', header: true })
115+
lifeSteps.unshift({ label: 'Étapes de vie', header: true })
126116
return [
127-
...lifeSteps.value,
117+
...lifeSteps,
128118
...ticketTypeOptions,
129119
// ...categories,
130120
...states

app/src/components/searchfilters/index.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<template lang="pug" @keypress="addFilter">
1+
<template lang="pug" @keypress.enter="addFilter">
22
.q-ma-sm
33
tk-SearchfiltersMain(ref="mainData")
44
//Filters chips
Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,31 @@
11
<template lang="pug">
2-
q-card.bg-gray-4
3-
q-card-section
4-
.text-h6 Autres sections
5-
q-separator(inset)
6-
q-card-actions(align="center" vertical)
7-
q-btn(color="primary" flat) Mes tickets
8-
q-btn(color="primary" flat) Tout les tickets
9-
q-separator(inset)
10-
q-card-actions(align="center" vertical)
11-
q-btn(v-for="projet in projets.data" color="primary" flat :key="projet.id" :to="`/ticket/${projet.id}`") {{ projet.name }}
12-
2+
div
3+
q-toolbar
4+
q-breadcrumbs
5+
q-breadcrumbs-el(to="/") Accueil
6+
q-breadcrumbs-el(to="/tickets") Tickets
7+
q-breadcrumbs-el(:label="sequence")
8+
q-expansion-item(label="Autres sections").bg-gray-4
9+
q-card.bg-gray-4
10+
q-separator(inset)
11+
q-card-actions(align="center" vertical)
12+
q-btn(color="primary" flat) Mes tickets
13+
q-btn(color="primary" flat) Tout les tickets
14+
q-separator(inset)
15+
q-card-actions(align="center" vertical)
16+
q-btn(v-for="projet in projets.data" color="primary" flat :key="projet.id" :to="`/ticket/${projet.id}`") {{ projet.name }}
1317
</template>
1418

1519
<script lang="ts" setup>
1620
import { ref, onMounted, computed } from 'vue'
1721
import { useHttpApi } from '~/composables/useHttpApi';
22+
1823
const { data: projets } = await useHttpApi(`core/project`)
24+
defineProps({
25+
sequence: {
26+
type: String,
27+
required: true
28+
}
29+
})
1930
2031
</script>
Lines changed: 160 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,161 @@
11
<template lang="pug">
2-
3-
</template>
2+
q-card(style="height: 100%").column
3+
q-toolbar.col-1
4+
q-toolbar-title {{ sequence }} | {{ subject }}
5+
q-scroll-area.col-8(ref="chatScroll")
6+
div(v-for='(value, key) in getMessageByDay' :key='key')
7+
q-chat-message
8+
template(v-slot:label)
9+
span {{ key }}
10+
q-separator(inset)
11+
div(v-for="(message, index) in value" :id="index").q-mx-md
12+
//- div(v-for="fragment in message.fragments")
13+
q-chat-message(
14+
:sent="message.metadata.createdBy === user.username"
15+
:name="message.metadata.createdBy" size="10"
16+
)
17+
template(v-slot:stamp)
18+
span {{ getTimeFrom(message.metadata.createdAt) }}
19+
q-tooltip.text-body2 {{ getHour(message.metadata.createdAt) }}
20+
template(v-slot:default)
21+
div
22+
q-chip(v-for='file in message.fragments.file' :key='file.id' icon="mdi-paperclip" dense size='md' :label="file.message")
23+
q-separator.q-my-xs(v-if="message.fragments.file")
24+
div(v-for='raw in message.fragments.raw' v-html="raw.message")
25+
26+
q-editor(
27+
v-model="message" placeholder="Votre message ..." v-model:fullscreen="isFullscreen"
28+
:definitions="{ send: { tip: 'Envoyer', icon: 'mdi-send', label: 'Envoyer', handler: sendMessage }, attach:{ tip: 'Joindre un fichier', icon: 'mdi-paperclip', label: 'Joindre un fichier', handler: () => {console.log('joindre')} }}"
29+
:toolbar="[['left','center','right','justify'],['bold','italic','underline','strike'],['undo','redo'],['attach','send']]"
30+
).col
31+
.col-1(ref="dropZoneRef").bg-grey-3.items-center.justify-center.q-pa-md
32+
q-icon(name="mdi-paperclip" size="md" :class="isOverDropZone ? 'text-primary' : 'text-grey-5'")
33+
span.q-ml-md(:class="isOverDropZone ? 'text-primary' : 'text-grey-5'") Déposer un fichier
34+
35+
</template>
36+
37+
<script lang="ts" setup>
38+
import { ref, onMounted, computed } from 'vue'
39+
import { useRoute } from 'nuxt/app';
40+
import { useHttpApi } from '~/composables/useHttpApi';
41+
import { useDayjs, usePinia } from "#imports";
42+
import { generateMongoId } from '~/utils';
43+
import type { Fragments, Threads } from '~/types';
44+
import type { components } from '#build/types/service-api'
45+
import { ThreadType } from '~/utils';
46+
import { useDropZone } from '@vueuse/core'
47+
48+
type ThreadDto = components['schemas']['ThreadDto']
49+
type FragmentPartDto = components["schemas"]["FragmentPartDto"]
50+
type ThreadCreateDto = components['schemas']['ThreadCreateDto']
51+
52+
const dayjs = useDayjs()
53+
54+
const store = usePinia()
55+
const user = store.state.value.auth.user
56+
const props = defineProps({
57+
sequence: {
58+
type: String,
59+
required: true
60+
},
61+
subject: {
62+
type: String,
63+
required: true
64+
}
65+
})
66+
67+
const chatScroll = ref(null)
68+
onMounted(() => {
69+
scroll()
70+
})
71+
72+
const route = useRoute()
73+
const query = ref({
74+
"filters[ticketId]": `${route.params.id}`,
75+
"sort[metadata.createdAt]": 'asc',
76+
"limit": 100,
77+
})
78+
79+
const isFullscreen = ref(false)
80+
81+
const dropZoneRef = ref<HTMLDivElement>()
82+
const { data: threads } = await useHttpApi(`tickets/thread`, {
83+
method: 'get',
84+
query
85+
})
86+
const onDrop = (files: File[] | null) => {
87+
console.log('drop')
88+
console.log(files)
89+
}
90+
const { isOverDropZone } = useDropZone(dropZoneRef, onDrop)
91+
92+
const getTimeFrom = (time: string) => {
93+
return dayjs().to(dayjs(time))
94+
}
95+
96+
const getHour = (time: string) => {
97+
return dayjs(time).format('DD-MM-YYYY HH:mm')
98+
}
99+
100+
const scroll = () => {
101+
const target = chatScroll.value?.getScrollTarget()
102+
chatScroll.value?.setScrollPosition('vertical', target.scrollHeight, 0)
103+
}
104+
105+
const getThreadFragments = (thread: ThreadDto): Fragments => {
106+
return thread.fragments.reduce((acc: Fragments, fragment) => {
107+
const disposition = fragment.disposition
108+
if (!acc[disposition]) acc[disposition] = []
109+
acc[disposition]?.push(fragment)
110+
return acc as Fragments
111+
}, {})
112+
}
113+
114+
const sendMessage = async () => {
115+
const payload: ThreadCreateDto = {
116+
ticketId: generateMongoId(route.params.id.toString()),
117+
fragments: [
118+
{
119+
id: generateMongoId(),
120+
disposition: 'raw',
121+
message: message.value
122+
}
123+
],
124+
metadata: {
125+
createdBy: user.username,
126+
createdAt: dayjs().toISOString(),
127+
lastUpdatedAt: dayjs().toISOString(),
128+
lastUpdatedBy: user.username
129+
},
130+
type: ThreadType.OUTGOING
131+
}
132+
const { data: thread } = await useHttpApi(`tickets/thread`, {
133+
method: 'post',
134+
body: payload
135+
})
136+
threads.value?.data.push(thread.value?.data)
137+
message.value = ''
138+
scroll()
139+
}
140+
141+
const getMessageByDay = computed((): Threads => {
142+
return threads.value?.data.reduce((acc: Threads, thread: ThreadDto) => {
143+
const day = dayjs(thread.metadata.createdAt).format('DD-MM-YYYY')
144+
const newTread = {
145+
...thread,
146+
fragments: getThreadFragments(thread)
147+
}
148+
if (!acc[day]) acc[day] = []
149+
acc[day].push(newTread)
150+
return acc
151+
}, {})
152+
})
153+
154+
const message = ref('')
155+
</script>
156+
157+
<style lang="css" scoped>
158+
.mainContent {
159+
height: calc(100% - 50px);
160+
}
161+
</style>

0 commit comments

Comments
 (0)