Compare commits
	
		
			No commits in common. "3f4f8a5fd4a926b59f65cf9c1f20d3644e9964dc" and "94c45fe3f4336a98389f935b16cdbe6dea687f4d" have entirely different histories.
		
	
	
		
			3f4f8a5fd4
			...
			94c45fe3f4
		
	
		|  | @ -1,6 +1,6 @@ | ||||||
| { | { | ||||||
|   "license": "MIT", |   "license": "MIT", | ||||||
|   "version": "1.1.0", |   "version": "1.0.0", | ||||||
|   "name": "@flaschengeist/users", |   "name": "@flaschengeist/users", | ||||||
|   "author": "Ferdinand Thiessen <rpm@fthiessen.de>", |   "author": "Ferdinand Thiessen <rpm@fthiessen.de>", | ||||||
|   "homepage": "https://flaschengeist.dev/Flaschengeist", |   "homepage": "https://flaschengeist.dev/Flaschengeist", | ||||||
|  |  | ||||||
|  | @ -1,66 +0,0 @@ | ||||||
| <template> |  | ||||||
|   <q-card> |  | ||||||
|     <q-card-section class="text-h6"> Einstellungen Benutzer </q-card-section> |  | ||||||
|     <q-card-section> |  | ||||||
|       <q-select |  | ||||||
|         v-model="displayNameMode" |  | ||||||
|         :options="options" |  | ||||||
|         label="Anzeige des Namens" |  | ||||||
|         emit-value |  | ||||||
|         map-options |  | ||||||
|         input-debounce="0" |  | ||||||
|         filled |  | ||||||
|       /> |  | ||||||
|     </q-card-section> |  | ||||||
|   </q-card> |  | ||||||
| </template> |  | ||||||
| <script lang="ts"> |  | ||||||
| import { defineComponent, onBeforeMount, computed } from 'vue'; |  | ||||||
| import { useUserStore } from '@flaschengeist/api'; |  | ||||||
| import { DisplayNameMode } from '../models'; |  | ||||||
| export default defineComponent({ |  | ||||||
|   name: 'SettingWidget', |  | ||||||
|   setup() { |  | ||||||
|     const store = useUserStore(); |  | ||||||
|     onBeforeMount(() => { |  | ||||||
|       void store.getDisplayNameModeSetting(true); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     const displayNameMode = computed({ |  | ||||||
|       get: () => store.userSettings.display_name || DisplayNameMode.DISPLAYNAME, |  | ||||||
|       set: (val) => { |  | ||||||
|         console.log('set', val); |  | ||||||
|         void store.setDisplayNameModeSetting(val); |  | ||||||
|       }, |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     const options = [ |  | ||||||
|       { |  | ||||||
|         label: 'Anzeigename', |  | ||||||
|         value: DisplayNameMode.DISPLAYNAME, |  | ||||||
|       }, |  | ||||||
|       { |  | ||||||
|         label: 'Vorname', |  | ||||||
|         value: DisplayNameMode.FIRSTNAME, |  | ||||||
|       }, |  | ||||||
|       { |  | ||||||
|         label: 'Nachname', |  | ||||||
|         value: DisplayNameMode.LASTNAME, |  | ||||||
|       }, |  | ||||||
|       { |  | ||||||
|         label: 'Vor- und Nachname', |  | ||||||
|         value: DisplayNameMode.FIRSTNAME_LASTNAME, |  | ||||||
|       }, |  | ||||||
|       { |  | ||||||
|         label: 'Nachname, Vorname', |  | ||||||
|         value: DisplayNameMode.LASTNAME_FIRSTNAME, |  | ||||||
|       }, |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     return { |  | ||||||
|       displayNameMode, |  | ||||||
|       options, |  | ||||||
|     }; |  | ||||||
|   }, |  | ||||||
| }); |  | ||||||
| </script> |  | ||||||
|  | @ -4,19 +4,15 @@ | ||||||
|     filled |     filled | ||||||
|     :label="label" |     :label="label" | ||||||
|     :options="users" |     :options="users" | ||||||
|     :option-label="showName" |     option-label="display_name" | ||||||
|     option-value="userid" |     option-value="userid" | ||||||
|     map-options |     map-options | ||||||
|     use-input |  | ||||||
|     input-debounce="0" |  | ||||||
|     @filter="filterFn" |  | ||||||
|   /> |   /> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import { computed, defineComponent, PropType, onBeforeMount, ref } from 'vue'; | import { computed, defineComponent, PropType, onBeforeMount } from 'vue'; | ||||||
| import { useUserStore } from '@flaschengeist/api'; | import { useUserStore } from '@flaschengeist/api'; | ||||||
| import { DisplayNameMode } from '../models'; |  | ||||||
| 
 | 
 | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
|   name: 'UserSelector', |   name: 'UserSelector', | ||||||
|  | @ -30,64 +26,17 @@ export default defineComponent({ | ||||||
| 
 | 
 | ||||||
|     onBeforeMount(() => { |     onBeforeMount(() => { | ||||||
|       void userStore.getUsers(false); |       void userStore.getUsers(false); | ||||||
|       void userStore.getDisplayNameModeSetting(true); |  | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     const users = computed(() => |     const users = computed(() => userStore.users); | ||||||
|       userStore.users.filter((user) => { |  | ||||||
|         let names = filter.value.toLowerCase().split(' '); |  | ||||||
|         if (names.length < 1) { |  | ||||||
|           return true; |  | ||||||
|         } |  | ||||||
|         if (names.length === 1) { |  | ||||||
|           let name = names[0]; |  | ||||||
|           return ( |  | ||||||
|             user.lastname.toLowerCase().includes(name) || |  | ||||||
|             user.firstname.toLowerCase().includes(name) |  | ||||||
|           ); |  | ||||||
|         } |  | ||||||
|         if (names.length === 2) { |  | ||||||
|           let name1 = names[0]; |  | ||||||
|           let name2 = names[1]; |  | ||||||
|           return ( |  | ||||||
|             (user.lastname.toLowerCase().includes(name1) && |  | ||||||
|               user.firstname.toLowerCase().includes(name2)) || |  | ||||||
|             (user.lastname.toLowerCase().includes(name2) && |  | ||||||
|               user.firstname.toLowerCase().includes(name1)) |  | ||||||
|           ); |  | ||||||
|         } |  | ||||||
|         return true; |  | ||||||
|       }) |  | ||||||
|     ); |  | ||||||
|     const filter = ref<string>(''); |  | ||||||
|     const filterFn = (val: string, update: () => void) => { |  | ||||||
|       filter.value = val; |  | ||||||
|       update(); |  | ||||||
|     }; |  | ||||||
|     const selected = computed({ |     const selected = computed({ | ||||||
|       get: () => props.modelValue, |       get: () => props.modelValue, | ||||||
|       set: (value: FG.User | undefined) => (value ? emit('update:modelValue', value) : undefined), |       set: (value: FG.User | undefined) => (value ? emit('update:modelValue', value) : undefined), | ||||||
|     }); |     }); | ||||||
|     function showName(user: FG.User) { |  | ||||||
|       switch (userStore.userSettings.display_name) { |  | ||||||
|         case DisplayNameMode.DISPLAYNAME: |  | ||||||
|           return user.display_name; |  | ||||||
|         case DisplayNameMode.FIRSTNAME: |  | ||||||
|           return user.firstname; |  | ||||||
|         case DisplayNameMode.LASTNAME: |  | ||||||
|           return user.lastname; |  | ||||||
|         case DisplayNameMode.FIRSTNAME_LASTNAME: |  | ||||||
|           return `${user.firstname} ${user.lastname}`; |  | ||||||
|         case DisplayNameMode.LASTNAME_FIRSTNAME: |  | ||||||
|           return `${user.lastname}, ${user.firstname}`; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     return { |     return { | ||||||
|       selected, |       selected, | ||||||
|       users, |       users, | ||||||
|       filterFn, |  | ||||||
|       showName, |  | ||||||
|     }; |     }; | ||||||
|   }, |   }, | ||||||
| }); | }); | ||||||
|  |  | ||||||
							
								
								
									
										10
									
								
								src/index.ts
								
								
								
								
							
							
						
						
									
										10
									
								
								src/index.ts
								
								
								
								
							|  | @ -1,7 +1,6 @@ | ||||||
| import { FG_Plugin } from '@flaschengeist/types'; | import { FG_Plugin } from '@flaschengeist/types'; | ||||||
| import { defineAsyncComponent } from 'vue'; | import { defineAsyncComponent } from 'vue'; | ||||||
| import routes from './routes'; | import routes from './routes'; | ||||||
| import { DisplayNameMode } from './models'; |  | ||||||
| 
 | 
 | ||||||
| const plugin: FG_Plugin.Plugin = { | const plugin: FG_Plugin.Plugin = { | ||||||
|   id: 'users', |   id: 'users', | ||||||
|  | @ -17,15 +16,6 @@ const plugin: FG_Plugin.Plugin = { | ||||||
|       widget: defineAsyncComponent(() => import('./components/Widget.vue')), |       widget: defineAsyncComponent(() => import('./components/Widget.vue')), | ||||||
|     }, |     }, | ||||||
|   ], |   ], | ||||||
|   settingWidgets: [ |  | ||||||
|     { |  | ||||||
|       priority: 1, |  | ||||||
|       name: 'userSettings', |  | ||||||
|       permissions: [], |  | ||||||
|       widget: defineAsyncComponent(() => import('./components/SettingWidget.vue')), |  | ||||||
|     }, |  | ||||||
|   ], |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export default plugin; | export default plugin; | ||||||
| export { DisplayNameMode }; |  | ||||||
|  |  | ||||||
|  | @ -12,12 +12,3 @@ export interface LoginResponse { | ||||||
| export interface CurrentUserResponse extends FG.User { | export interface CurrentUserResponse extends FG.User { | ||||||
|   permissions: FG.Permission[]; |   permissions: FG.Permission[]; | ||||||
| } | } | ||||||
| 
 |  | ||||||
| export enum DisplayNameMode { |  | ||||||
|   FIRSTNAME = 'firstname', |  | ||||||
|   LASTNAME = 'lastname', |  | ||||||
|   FULLNAME = 'fullname', |  | ||||||
|   DISPLAYNAME = 'display_name', |  | ||||||
|   FIRSTNAME_LASTNAME = 'firstname_lastname', |  | ||||||
|   LASTNAME_FIRSTNAME = 'lastname_firstname', |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -1,153 +0,0 @@ | ||||||
| <template> |  | ||||||
|   <q-page> |  | ||||||
|     <q-table |  | ||||||
|       :columns="cols" |  | ||||||
|       :rows="users" |  | ||||||
|       :filter="filter" |  | ||||||
|       :grid="grid" |  | ||||||
|       :pagination="initialPagination" |  | ||||||
|     > |  | ||||||
|       <template v-slot:body-cell-avatar="props"> |  | ||||||
|         <q-td :key="props.key" :props="props"> |  | ||||||
|           <user-avatar v-model="props.row" /> |  | ||||||
|         </q-td> |  | ||||||
|       </template> |  | ||||||
|       <template v-slot:top-right="props"> |  | ||||||
|         <q-input v-model="filter" filled dense debounce="300" placeholder="Suche"> |  | ||||||
|           <template v-slot:append> |  | ||||||
|             <q-icon name="mdi-magnify" /> |  | ||||||
|           </template> |  | ||||||
|         </q-input> |  | ||||||
|         <q-btn |  | ||||||
|           flat |  | ||||||
|           round |  | ||||||
|           dense |  | ||||||
|           :icon="grid ? 'mdi-land-rows-horizontal' : 'mdi-card-account-details'" |  | ||||||
|           @click="grid = !grid" |  | ||||||
|         /> |  | ||||||
|       </template> |  | ||||||
|       <template v-slot:item="props"> |  | ||||||
|         <div class="q-pa-xs col-xs-12 col-sm-6 col-md-4"> |  | ||||||
|           <q-card bordered> |  | ||||||
|             <div class="row justify-center"> |  | ||||||
|               <q-img :src="avatarURL(props.row.userid)"> |  | ||||||
|                 <template #error> |  | ||||||
|                   <div |  | ||||||
|                     class="row fit justify-center items-center" |  | ||||||
|                     style="background-color: transparent" |  | ||||||
|                   > |  | ||||||
|                     <img src="no-image.svg" style="object-fit: contain; height: 100%" /> |  | ||||||
|                   </div> |  | ||||||
|                 </template> |  | ||||||
|               </q-img> |  | ||||||
|             </div> |  | ||||||
|             <q-card-section> |  | ||||||
|               <div class="text-h6">{{ props.row.firstname }} {{ props.row.lastname }}</div> |  | ||||||
|               <div class="text-caption">{{ props.row.display_name }}</div> |  | ||||||
|             </q-card-section> |  | ||||||
|             <q-card-section> |  | ||||||
|               <div class="row items-center"> |  | ||||||
|                 <q-btn |  | ||||||
|                   flat |  | ||||||
|                   dense |  | ||||||
|                   icon="mdi-email" |  | ||||||
|                   :href="'mailto:' + props.row.mail" |  | ||||||
|                   class="q-mr-xs" |  | ||||||
|                   no-caps |  | ||||||
|                 /> |  | ||||||
|                 <div class="text-caption">{{ props.row.mail }}</div> |  | ||||||
|               </div> |  | ||||||
|               <div class="row items-center"> |  | ||||||
|                 <q-btn |  | ||||||
|                   flat |  | ||||||
|                   dense |  | ||||||
|                   icon="mdi-calendar" |  | ||||||
|                   class="q-mr-xs" |  | ||||||
|                   no-caps |  | ||||||
|                   v-if="props.row.birthday" |  | ||||||
|                 /> |  | ||||||
|                 <div class="text-caption" v-if="props.row.birthday"> |  | ||||||
|                   {{ props.row.birthday.toLocaleDateString() }} |  | ||||||
|                 </div> |  | ||||||
|               </div> |  | ||||||
|             </q-card-section> |  | ||||||
|           </q-card> |  | ||||||
|         </div> |  | ||||||
|       </template> |  | ||||||
|     </q-table> |  | ||||||
|   </q-page> |  | ||||||
| </template> |  | ||||||
| <script lang="ts"> |  | ||||||
| import { defineComponent, onBeforeMount, computed, ref } from 'vue'; |  | ||||||
| import { useUserStore, avatarURL } from '@flaschengeist/api'; |  | ||||||
| import { UserAvatar } from '@flaschengeist/api/components'; |  | ||||||
| 
 |  | ||||||
| const cols = [ |  | ||||||
|   { |  | ||||||
|     name: 'avatar', |  | ||||||
|     label: 'Profilbild', |  | ||||||
|     sortable: false, |  | ||||||
|   }, |  | ||||||
|   { |  | ||||||
|     name: 'firstName', |  | ||||||
|     label: 'Vorname', |  | ||||||
|     field: 'firstname', |  | ||||||
|     sortable: true, |  | ||||||
|   }, |  | ||||||
|   { |  | ||||||
|     name: 'lastName', |  | ||||||
|     label: 'Nachname', |  | ||||||
|     field: 'lastname', |  | ||||||
|     sortable: true, |  | ||||||
|   }, |  | ||||||
|   { |  | ||||||
|     name: 'displayName', |  | ||||||
|     label: 'Anzeigename', |  | ||||||
|     field: 'display_name', |  | ||||||
|     sortable: true, |  | ||||||
|   }, |  | ||||||
|   { |  | ||||||
|     name: 'mail', |  | ||||||
|     label: 'E-Mail', |  | ||||||
|     field: 'mail', |  | ||||||
|     sortable: true, |  | ||||||
|   }, |  | ||||||
|   { |  | ||||||
|     name: 'birthday', |  | ||||||
|     label: 'Geburtstag', |  | ||||||
|     field: 'birthday', |  | ||||||
|     sortable: true, |  | ||||||
|     format: (val: Date | undefined) => { |  | ||||||
|       return val?.toLocaleDateString(); |  | ||||||
|     }, |  | ||||||
|   }, |  | ||||||
| ]; |  | ||||||
| 
 |  | ||||||
| export default defineComponent({ |  | ||||||
|   components: { UserAvatar }, |  | ||||||
|   setup() { |  | ||||||
|     const userStore = useUserStore(); |  | ||||||
|     onBeforeMount(() => { |  | ||||||
|       void userStore.getUsers(true); |  | ||||||
|     }); |  | ||||||
|     const filter = ref(''); |  | ||||||
| 
 |  | ||||||
|     const users = computed(() => userStore.users); |  | ||||||
|     const grid = ref(false); |  | ||||||
|     return { |  | ||||||
|       users, |  | ||||||
|       cols, |  | ||||||
|       filter, |  | ||||||
|       grid, |  | ||||||
|       avatarURL, |  | ||||||
|       initialPagination: { |  | ||||||
|         sortBy: 'lastName', |  | ||||||
|         descending: false, |  | ||||||
|         page: 1, |  | ||||||
|         rowsPerPage: 0, |  | ||||||
|         // rowsNumber: xx if getting data from a server |  | ||||||
|       }, |  | ||||||
|     }; |  | ||||||
|   }, |  | ||||||
| }); |  | ||||||
| </script> |  | ||||||
|  | @ -7,9 +7,6 @@ | ||||||
|         </q-card-section> |         </q-card-section> | ||||||
|         <MainUserSettings :user="currentUser" @update:user="updateUser" /> |         <MainUserSettings :user="currentUser" @update:user="updateUser" /> | ||||||
|       </q-card> |       </q-card> | ||||||
|       <div v-for="(item, index) in widgets" :key="index" class="full-height col-12"> |  | ||||||
|         <component :is="item.widget" /> |  | ||||||
|       </div> |  | ||||||
|       <div class="col-12 text-left text-h6">Aktive Sessions:</div> |       <div class="col-12 text-left text-h6">Aktive Sessions:</div> | ||||||
|       <user-session |       <user-session | ||||||
|         v-for="(session, index) in sessions" |         v-for="(session, index) in sessions" | ||||||
|  | @ -17,17 +14,15 @@ | ||||||
|         v-model="sessions[index]" |         v-model="sessions[index]" | ||||||
|         @delete="removeSession(session)" |         @delete="removeSession(session)" | ||||||
|       /> |       /> | ||||||
|       <q-btn label="List Widgets" @click="listWidgets" /> |  | ||||||
|     </q-page> |     </q-page> | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import { useMainStore, useUserStore, useSessionStore, hasPermissions } from '@flaschengeist/api'; | import { useMainStore, useUserStore, useSessionStore } from '@flaschengeist/api'; | ||||||
| import MainUserSettings from '../components/settings/MainUserSettings.vue'; | import MainUserSettings from '../components/settings/MainUserSettings.vue'; | ||||||
| import { defineComponent, onBeforeMount, ref, computed, inject } from 'vue'; | import { defineComponent, onBeforeMount, ref } from 'vue'; | ||||||
| import UserSession from '../components/settings/UserSession.vue'; | import UserSession from '../components/settings/UserSession.vue'; | ||||||
| import { FG_Plugin } from '@flaschengeist/types'; |  | ||||||
| import { Notify } from 'quasar'; | import { Notify } from 'quasar'; | ||||||
| 
 | 
 | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
|  | @ -52,29 +47,17 @@ export default defineComponent({ | ||||||
|         progress: true, |         progress: true, | ||||||
|         actions: [{ icon: 'mdi-close', color: 'white' }], |         actions: [{ icon: 'mdi-close', color: 'white' }], | ||||||
|       }); |       }); | ||||||
|       console.log(widgets); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     function removeSession(s: FG.Session) { |     function removeSession(s: FG.Session) { | ||||||
|       sessions.value = sessions.value.filter((ss) => ss.token !== s.token); |       sessions.value = sessions.value.filter((ss) => ss.token !== s.token); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const flaschengeist = inject<FG_Plugin.Flaschengeist>('flaschengeist'); |  | ||||||
|     const widgets = computed(() => |  | ||||||
|       flaschengeist?.settingWidgets.filter((widget) => hasPermissions(widget.permissions)) |  | ||||||
|     ); |  | ||||||
| 
 |  | ||||||
|     function listWidgets() { |  | ||||||
|       console.log(widgets); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return { |     return { | ||||||
|       currentUser, |       currentUser, | ||||||
|       sessions, |       sessions, | ||||||
|       updateUser, |       updateUser, | ||||||
|       removeSession, |       removeSession, | ||||||
|       widgets, |  | ||||||
|       listWidgets, |  | ||||||
|     }; |     }; | ||||||
|   }, |   }, | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -10,16 +10,6 @@ const mainRoutes: FG_Plugin.MenuRoute[] = [ | ||||||
|     permissions: ['user'], |     permissions: ['user'], | ||||||
|     route: { path: 'user', name: 'user', redirect: { name: 'user-settings' } }, |     route: { path: 'user', name: 'user', redirect: { name: 'user-settings' } }, | ||||||
|     children: [ |     children: [ | ||||||
|       { |  | ||||||
|         title: 'Mitglieder', |  | ||||||
|         icon: 'mdi-account-multiple', |  | ||||||
|         shortcut: true, |  | ||||||
|         route: { |  | ||||||
|           path: 'members', |  | ||||||
|           name: 'user-members', |  | ||||||
|           component: () => import('../pages/Members.vue'), |  | ||||||
|         }, |  | ||||||
|       }, |  | ||||||
|       { |       { | ||||||
|         title: 'Einstellungen', |         title: 'Einstellungen', | ||||||
|         icon: 'mdi-account-edit', |         icon: 'mdi-account-edit', | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue