@simsustech/quasar-components 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,279 @@
1
+ <template>
2
+ <q-table
3
+ v-if="modelValue"
4
+ v-model:pagination="tablePagination"
5
+ :title="lang.account.title"
6
+ class="full-width"
7
+ :rows="modelValue"
8
+ :columns="accountColumns"
9
+ row-key="id"
10
+ @request="onRequest"
11
+ >
12
+ <template #header="props">
13
+ <q-tr :props="props">
14
+ <q-th v-for="col in props.cols" :key="col.name" :props="props">
15
+ {{ col.label }}
16
+ </q-th>
17
+ </q-tr>
18
+ </template>
19
+ <template #top-right>
20
+ <q-btn icon="search" flat>
21
+ <q-menu>
22
+ <div class="q-pa-sm">
23
+ <q-input v-model="name" :label="lang.account.fields.name">
24
+ <template #append>
25
+ <q-icon
26
+ v-if="name"
27
+ name="cancel"
28
+ @click="name = ''"
29
+ class="q-field__focusable-action"
30
+ role="button"
31
+ />
32
+ </template>
33
+ </q-input>
34
+ <q-input v-model="email" :label="lang.account.fields.email">
35
+ <template #append>
36
+ <q-icon
37
+ v-if="email"
38
+ name="cancel"
39
+ @click="email = ''"
40
+ class="q-field__focusable-action"
41
+ role="button"
42
+ />
43
+ </template>
44
+ </q-input>
45
+
46
+ <q-select
47
+ v-model="roles"
48
+ multiple
49
+ style="min-width: 200px"
50
+ :label="lang.account.fields.roles"
51
+ :options="roleOptions"
52
+ map-options
53
+ emit-value
54
+ >
55
+ <template #append>
56
+ <q-icon
57
+ v-if="roles.length"
58
+ name="cancel"
59
+ @click="roles = []"
60
+ class="q-field__focusable-action"
61
+ role="button"
62
+ />
63
+ </template>
64
+ </q-select>
65
+ </div>
66
+ </q-menu>
67
+ </q-btn>
68
+ </template>
69
+ <template #body="props">
70
+ <q-tr :props="props">
71
+ <q-td v-for="col in props.cols" :key="col.name" :props="props">
72
+ {{ col.value }}
73
+ </q-td>
74
+ <q-td auto-width>
75
+ <q-btn size="sm" round flat icon="more_vert">
76
+ <q-menu>
77
+ <q-list>
78
+ <q-item clickable @click="openAddRoleDialog(props.row)">
79
+ <q-item-section>
80
+ <q-item-label>
81
+ {{ lang.account.labels.addRole }}
82
+ </q-item-label>
83
+ </q-item-section>
84
+ </q-item>
85
+ <q-item clickable @click="openRemoveRoleDialog(props.row)">
86
+ <q-item-section>
87
+ <q-item-label>
88
+ {{ lang.account.labels.removeRole }}
89
+ </q-item-label>
90
+ </q-item-section>
91
+ </q-item>
92
+ </q-list>
93
+ </q-menu>
94
+ </q-btn>
95
+ </q-td>
96
+ </q-tr>
97
+ </template>
98
+ </q-table>
99
+ </template>
100
+
101
+ <script lang="ts" setup generic="T extends Account">
102
+ import { computed, ref, toRefs, watch } from 'vue'
103
+ import { QTable, QTableColumn, useQuasar } from 'quasar'
104
+ import { useLang } from './lang/index.js'
105
+
106
+ export interface Account {
107
+ id: number
108
+ name?: string
109
+ email: string
110
+ roles: string[]
111
+ }
112
+
113
+ interface Pagination {
114
+ limit: number
115
+ offset: number
116
+ sortBy: keyof Account
117
+ descending: boolean
118
+ }
119
+
120
+ interface Criteria {
121
+ roles: string[]
122
+ }
123
+ interface Props {
124
+ modelValue: T[]
125
+ count: number
126
+ pagination: Pagination
127
+ mappedRoles: Record<string, string> // value: label
128
+ columns?: QTableColumn[]
129
+ }
130
+
131
+ const props = defineProps<Props>()
132
+
133
+ const emit = defineEmits<{
134
+ (e: 'update:pagination', pagination: Pagination): void
135
+ (e: 'update:criteria', criteria: Criteria): void
136
+ (e: 'addRole', { id, role }: { id: number; role: string }): void
137
+ (e: 'removeRole', { id, role }: { id: number; role: string }): void
138
+ }>()
139
+
140
+ const { modelValue, count, mappedRoles, columns } = toRefs(props)
141
+
142
+ // const { useQuery } = await createUseTrpc()
143
+ const lang = useLang()
144
+ const $q = useQuasar()
145
+
146
+ const sortBy = ref<keyof Account>(props.pagination.sortBy)
147
+ const descending = ref(props.pagination.descending)
148
+ const rowsPerPage = ref(props.pagination.limit)
149
+ const page = ref(props.pagination.offset / props.pagination.limit + 1)
150
+
151
+ const name = ref<string>('')
152
+ const email = ref<string>('')
153
+ const roles = ref<string[]>([])
154
+ const criteria = computed(() => ({
155
+ name: name.value,
156
+ email: email.value,
157
+ roles: roles.value
158
+ }))
159
+
160
+ const tablePagination = computed({
161
+ // getter
162
+ get() {
163
+ return {
164
+ sortBy: sortBy.value,
165
+ descending: descending.value,
166
+ page: page.value,
167
+ rowsPerPage: rowsPerPage.value,
168
+ rowsNumber: count.value || 0
169
+ }
170
+ },
171
+ // setter
172
+ set(newValue) {
173
+ sortBy.value = newValue.sortBy
174
+ descending.value = newValue.descending
175
+ page.value = newValue.page
176
+ rowsPerPage.value = newValue.rowsPerPage
177
+ }
178
+ })
179
+
180
+ watch(criteria, () => emit('update:criteria', criteria.value))
181
+
182
+ const onRequest: QTable['$props']['onRequest'] = ({ pagination }) => {
183
+ descending.value = pagination.descending
184
+ sortBy.value = pagination.sortBy as keyof Account
185
+ page.value = pagination.page
186
+ rowsPerPage.value = pagination.rowsPerPage
187
+
188
+ emit('update:pagination', {
189
+ limit: rowsPerPage.value,
190
+ offset: (page.value - 1) * rowsPerPage.value,
191
+ sortBy: sortBy.value as keyof Account,
192
+ descending: descending.value
193
+ })
194
+ return
195
+ }
196
+
197
+ const accountColumns = ref<QTableColumn[]>([
198
+ {
199
+ name: 'id',
200
+ required: true,
201
+ label: '#',
202
+ align: 'left',
203
+ field: (row) => row.id,
204
+ format: (val) => `${val}`,
205
+ sortable: true
206
+ },
207
+ {
208
+ name: 'name',
209
+ required: true,
210
+ label: lang.value.account.fields.name,
211
+ align: 'left',
212
+ field: (row) => row.name,
213
+ format: (val) => `${val || ''}`,
214
+ sortable: false
215
+ },
216
+ {
217
+ name: 'email',
218
+ required: true,
219
+ label: lang.value.account.fields.email,
220
+ align: 'left',
221
+ field: (row) => row.email,
222
+ format: (val) => `${val}`,
223
+ sortable: false
224
+ },
225
+ {
226
+ name: 'roles',
227
+ required: true,
228
+ label: lang.value.account.fields.roles,
229
+ align: 'right',
230
+ field: (row) => row.roles,
231
+ format: (val) =>
232
+ val.map((role: string) => mappedRoles.value[role]).join(', ')
233
+ }
234
+ ])
235
+
236
+ if (columns?.value) accountColumns.value.push(...columns.value)
237
+
238
+ const roleOptions = computed(() =>
239
+ Object.entries(mappedRoles.value).map(([key, value]) => ({
240
+ label: value,
241
+ value: key
242
+ }))
243
+ )
244
+
245
+ const openAddRoleDialog = (account: Account) => {
246
+ $q.dialog({
247
+ message: lang.value.account.messages.addRole(account),
248
+ options: {
249
+ type: 'radio',
250
+ model: 'role',
251
+ items: roleOptions.value.filter(
252
+ (newRole) => !account.roles.includes(newRole.value)
253
+ )
254
+ },
255
+ cancel: true,
256
+ persistent: true
257
+ }).onOk((role: string) => {
258
+ emit('addRole', { id: account.id, role })
259
+ })
260
+ }
261
+
262
+ const openRemoveRoleDialog = (account: Account) => {
263
+ $q.dialog({
264
+ message: lang.value.account.messages.removeRole(account),
265
+ options: {
266
+ type: 'radio',
267
+ model: 'role',
268
+ items: account.roles?.map((role) => ({
269
+ label: mappedRoles.value[role],
270
+ value: role
271
+ }))
272
+ },
273
+ cancel: true,
274
+ persistent: true
275
+ }).onOk((role) => {
276
+ emit('removeRole', { id: account.id, role })
277
+ })
278
+ }
279
+ </script>
@@ -10,4 +10,5 @@ export { default as VerificationSlider } from './VerificationSlider.vue'
10
10
  export { default as ConsentList } from './ConsentList.vue'
11
11
  export { default as UserMenuButton } from './UserMenuButton.vue'
12
12
  export { default as LoginButton } from './LoginButton.vue'
13
+ export { default as AccountsTable } from './AccountsTable.vue'
13
14
  export { useLang, loadLang } from './lang/index.js'
@@ -90,6 +90,24 @@ const lang: Language = {
90
90
  },
91
91
  verification: {
92
92
  slider: 'Please drag the slider below all the way to the right.'
93
+ },
94
+ account: {
95
+ title: 'Accounts',
96
+ fields: {
97
+ name: 'Name',
98
+ email: 'Email',
99
+ roles: 'Roles'
100
+ },
101
+ labels: {
102
+ addRole: 'Add role',
103
+ removeRole: 'Remove role'
104
+ },
105
+ messages: {
106
+ addRole: ({ email }) =>
107
+ `Select the role which you want to add to ${email}.`,
108
+ removeRole: ({ email }) =>
109
+ `Select the role which you want to remove from ${email}.`
110
+ }
93
111
  }
94
112
  }
95
113
 
@@ -86,6 +86,22 @@ export interface Language {
86
86
  verification: {
87
87
  slider: string
88
88
  }
89
+ account: {
90
+ title: string
91
+ fields: {
92
+ name: string
93
+ email: string
94
+ roles: string
95
+ }
96
+ labels: {
97
+ addRole: string
98
+ removeRole: string
99
+ }
100
+ messages: {
101
+ addRole: ({ email }: { email: string }) => string
102
+ removeRole: ({ email }: { email: string }) => string
103
+ }
104
+ }
89
105
  }
90
106
 
91
107
  import type { Ref } from 'vue'
@@ -90,6 +90,24 @@ const lang: Language = {
90
90
  },
91
91
  verification: {
92
92
  slider: 'Sleep a.u.b. het onderstaande bolletje helemaal naar rechts.'
93
+ },
94
+ account: {
95
+ title: 'Accounts',
96
+ fields: {
97
+ name: 'Naam',
98
+ email: 'Email',
99
+ roles: 'Rollen'
100
+ },
101
+ labels: {
102
+ addRole: 'Rol toevoegen',
103
+ removeRole: 'Rol verwijderen'
104
+ },
105
+ messages: {
106
+ addRole: ({ email }) =>
107
+ `Selecteer de rol die u wilt toevoegen aan ${email}.`,
108
+ removeRole: ({ email }) =>
109
+ `Selecteer de rol die u wilt verwijderen van ${email}.`
110
+ }
93
111
  }
94
112
  }
95
113
 
@@ -3,7 +3,7 @@
3
3
  :model-value="modelValue"
4
4
  bottom-slots
5
5
  :rules="validations"
6
- :label="label"
6
+ :label="`${label}${required ? '*' : ''}`"
7
7
  stack-label
8
8
  >
9
9
  <template #control>
@@ -68,7 +68,7 @@ import { useLang } from './lang'
68
68
  export interface Props {
69
69
  modelValue: string | null
70
70
  format?: 'YYYY-MM-DD' | 'DD-MM-YYYY' | 'MM-DD-YYYY'
71
- locale?: QuasarLanguageCodes
71
+ locale?: QuasarLanguageCodes[number]
72
72
  label?: string
73
73
  required?: boolean
74
74
  clearable?: boolean
@@ -19,12 +19,8 @@
19
19
  @update:model-value="$emit('update:model-value', $event)"
20
20
  >
21
21
  <template #hint> {{ hint }} </template>
22
- <template
23
- v-for="(slot, index) of Object.keys($slots)"
24
- :key="index"
25
- #[slot]="scope"
26
- >
27
- <slot :scope="scope" :name="slot"></slot>
22
+ <template v-for="(_, slot) in $slots" v-slot:[slot]="scope">
23
+ <slot :name="slot" v-bind="scope || {}" />
28
24
  </template>
29
25
  </q-select>
30
26
  </template>
@@ -41,13 +37,15 @@ export default {
41
37
  generic="T extends { id: number; [key: string]: unknown }"
42
38
  >
43
39
  import { QSelect } from 'quasar'
44
- import { computed, ref, toRefs, useAttrs, watch } from 'vue'
40
+ import { computed, ref, toRefs, useAttrs, watch, onMounted } from 'vue'
45
41
  import { useLang } from './lang/index.js'
46
42
 
47
43
  interface Props {
48
44
  modelValue?: number | number[] | null
49
45
  labelKey: string
46
+ labelFunction?: (option: unknown) => string
50
47
  valueKey?: string
48
+ extraFields?: string[]
51
49
  filteredOptions: T[]
52
50
  required?: boolean
53
51
  onFilter?: unknown
@@ -76,13 +74,32 @@ const emit = defineEmits<{
76
74
 
77
75
  const lang = useLang()
78
76
 
79
- const { modelValue, onFilter, filteredOptions, labelKey, valueKey } =
80
- toRefs(props)
77
+ const {
78
+ modelValue,
79
+ onFilter,
80
+ filteredOptions,
81
+ labelKey,
82
+ valueKey,
83
+ labelFunction,
84
+ extraFields
85
+ } = toRefs(props)
86
+
81
87
  const options = computed(() => {
82
88
  if (filteredOptions.value.length) {
83
89
  return filteredOptions.value?.map((option) => ({
84
- label: option[labelKey.value],
85
- value: option[valueKey.value || 'id']
90
+ label: labelFunction.value
91
+ ? labelFunction.value(option)
92
+ : option[labelKey.value],
93
+ value: option[valueKey.value || 'id'],
94
+ extraFields: extraFields.value?.reduce(
95
+ (result, key) => {
96
+ if (option.hasOwnProperty(key)) {
97
+ result[key] = option[key]
98
+ }
99
+ return result
100
+ },
101
+ {} as Record<string, unknown>
102
+ )
86
103
  }))
87
104
  }
88
105
  return []
@@ -122,4 +139,13 @@ watch(modelValue, () => {
122
139
  done: () => {}
123
140
  })
124
141
  })
142
+
143
+ onMounted(() => {
144
+ if (!options.value.length)
145
+ emit('filter', {
146
+ ids: selectedIds.value,
147
+ searchPhrase: '',
148
+ done: () => {}
149
+ })
150
+ })
125
151
  </script>
@@ -3,6 +3,7 @@
3
3
  v-bind="attrs"
4
4
  :options="languageOptions"
5
5
  :model-value="modelValue"
6
+ :label="`${lang.locale.locale}${required ? '*' : ''}`"
6
7
  emit-value
7
8
  map-options
8
9
  >
@@ -42,13 +43,17 @@ import { useAttrs, toRefs } from 'vue'
42
43
  import { QSelect } from 'quasar'
43
44
  import { nl, enUs } from '../flags/index.js'
44
45
  import { useLang as useFlagsLang } from '../flags/lang/index.js'
46
+ import { useLang } from './lang/index.js'
47
+
45
48
  export interface Props {
46
49
  modelValue?: string | null
50
+ required?: boolean
47
51
  }
48
52
  const props = defineProps<Props>()
49
53
  const { modelValue } = toRefs(props)
50
54
  const attrs = useAttrs()
51
55
 
56
+ const lang = useLang()
52
57
  const flagsLang = useFlagsLang()
53
58
  const languageOptions = [
54
59
  {
@@ -36,6 +36,7 @@ const lang: Language = {
36
36
  }
37
37
  },
38
38
  datePicker: {
39
+ date: 'Date',
39
40
  placeholder: 'YYYY/MM/DD',
40
41
  YYYY: 'YYYY',
41
42
  MM: 'MM',
@@ -48,6 +49,9 @@ const lang: Language = {
48
49
  currency: 'Currency',
49
50
  EUR: 'Euro',
50
51
  USD: 'USD'
52
+ },
53
+ locale: {
54
+ locale: 'Locale'
51
55
  }
52
56
  }
53
57
 
@@ -34,6 +34,7 @@ export interface Language {
34
34
  }
35
35
  }
36
36
  datePicker: {
37
+ date: string
37
38
  placeholder: string
38
39
  YYYY: string
39
40
  MM: string
@@ -47,6 +48,9 @@ export interface Language {
47
48
  EUR: string
48
49
  USD: string
49
50
  }
51
+ locale: {
52
+ locale: string
53
+ }
50
54
  }
51
55
 
52
56
  import type { Ref } from 'vue'
@@ -36,6 +36,7 @@ const lang: Language = {
36
36
  }
37
37
  },
38
38
  datePicker: {
39
+ date: 'Datum',
39
40
  placeholder: 'JJJJ/MM/DD',
40
41
  YYYY: 'JJJJ',
41
42
  MM: 'MM',
@@ -48,6 +49,9 @@ const lang: Language = {
48
49
  currency: 'Valuta',
49
50
  EUR: 'Euro',
50
51
  USD: 'USD'
52
+ },
53
+ locale: {
54
+ locale: 'Land'
51
55
  }
52
56
  }
53
57
 
@@ -8,6 +8,7 @@
8
8
  class="shadow-2"
9
9
  :class="{ 'bg-dark': $q.dark.isActive, 'bg-white': !$q.dark.isActive }"
10
10
  >
11
+ <slot name="fab" />
11
12
  <q-btn
12
13
  v-if="type === 'create'"
13
14
  :disable="disabled"
@@ -21,7 +22,7 @@
21
22
  @click="create"
22
23
  />
23
24
  <q-btn
24
- v-else
25
+ v-else-if="type === 'update'"
25
26
  :disable="disabled"
26
27
  flat
27
28
  style="margin-bottom: -50px; z-index: 5"
@@ -32,10 +33,10 @@
32
33
  class="q-mr-sm bg-primary text-white"
33
34
  @click="update"
34
35
  />
35
- <q-toolbar-title shrink>
36
+ <q-toolbar-title>
36
37
  <slot name="header" />
37
38
  </q-toolbar-title>
38
- <q-space />
39
+ <slot name="header-side" />
39
40
  </q-toolbar>
40
41
  </q-page-sticky>
41
42
  </q-page>
@@ -48,7 +49,7 @@ export default {
48
49
  </script>
49
50
 
50
51
  <script setup lang="ts">
51
- import { ref, toRefs, watch } from 'vue'
52
+ import { ref, toRefs, watch, useSlots } from 'vue'
52
53
  import { useQuasar } from 'quasar'
53
54
  import { useLang, loadLang } from './lang'
54
55
 
@@ -56,13 +57,9 @@ export interface Props {
56
57
  type?: 'create' | 'update'
57
58
  disabled?: boolean
58
59
  }
59
- // const props = defineProps<Props>()
60
- const props = withDefaults(defineProps<Props>(), {
61
- type: 'create',
62
- disabled: false
63
- })
60
+ const slots = useSlots()
61
+ const props = defineProps<Props>()
64
62
 
65
- // const attrs = useAttrs();
66
63
  const emit = defineEmits<{
67
64
  (
68
65
  e: 'create',
@@ -92,7 +89,9 @@ watch($q.lang, (val) => {
92
89
  loadLang($q.lang.isoName)
93
90
  })
94
91
 
95
- const { disabled } = toRefs(props)
92
+ const { type, disabled } = toRefs(props)
93
+
94
+ if (!type.value && !slots.fab) type.value = 'create'
96
95
 
97
96
  const done = () => ''
98
97
  const create = (evt: unknown) =>