docker build test
This commit is contained in:
parent
075c598a86
commit
5bb107e53a
39 changed files with 117 additions and 75 deletions
|
@ -2,3 +2,4 @@
|
|||
node_modules/
|
||||
dist/
|
||||
.git/
|
||||
.env
|
||||
|
|
|
@ -8,9 +8,9 @@ RUN npm install
|
|||
|
||||
COPY . /app
|
||||
|
||||
RUN npm run build
|
||||
RUN npm run build-only
|
||||
|
||||
FROM nginx:18-alpine as prod
|
||||
FROM nginx:stable-alpine AS prod
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
|
74
README.md
74
README.md
|
@ -1,22 +1,72 @@
|
|||
# member-administration-ui
|
||||
|
||||
Memberadministration
|
||||
Mitgliederverwaltung für Feuerwehren und Vereine.
|
||||
|
||||
## Einleitung
|
||||
|
||||
Dieses Repository dient zur Verwaltung der Mitgliederdaten. Es ist ein Frontend-Client, der auf die Daten des [member-administration-server Backends](https://forgejo.jk-effects.cloud/Ehrenamt/member-administration-server) zugreift. Die Webapp bietet eine Möglichkeit Mitgliederdaten zu verwalten, Protokolle zu schreiben und Kaledereinträge zu erstellen. Benutzer können eingeladen und Rollen zugewiesen werden.
|
||||
|
||||
Eine Demo dieser Seite finden Sie unter [https://ff-admin-demo.jk-effects.cloud](https://ff-admin-demo.jk-effects.cloud).
|
||||
|
||||
Für die Verwendung muss ein TOTP-Code eingegeben werden.
|
||||
|
||||
Die Zugangsdaten (Lesebeschränkt) sind:\
|
||||
EMAIL: demo-besucher\
|
||||
TOTP: ![alt text](demo-totp-qrcode.png)\
|
||||
TOTP-Code: FBMDAJKFOYQXM2DNH47GWWBGJ5KWOUCW
|
||||
|
||||
## Installation
|
||||
|
||||
### Requirements
|
||||
### Docker Compose Setup
|
||||
|
||||
1. Access to the internet
|
||||
Um den Container hochzufahren, erstellen Sie eine `docker-compose.yml` Datei mit folgendem Inhalt:
|
||||
|
||||
### Configuration
|
||||
```yaml
|
||||
version: "3"
|
||||
|
||||
1. Copy the .env.example file to .env and fill in the required information
|
||||
2. Install all packages via `npm install`
|
||||
3. Start the backend application
|
||||
4. Start the application
|
||||
5. Run `npm run dev` to run inside dev-environment
|
||||
services:
|
||||
ff-member-administration-app:
|
||||
image: docker.registry.jk-effects.cloud/ehrenamt/member-administration/app:latest
|
||||
container_name: ff_member_administration_ui
|
||||
restart: unless-stopped
|
||||
|
||||
### Usage
|
||||
#volumes:
|
||||
# - <volume|local path>/favicon.png:/app/public/favicon.png
|
||||
# - <volume|local path>/favicon.png:/app/public/FFW-Logo.svg
|
||||
```
|
||||
|
||||
1. Open the browser and navigate to `http://localhost:5173` or the URL you specified in the server configuration
|
||||
2. Go to route `/setup` to create the first user (this path is disabled after the first user is created)
|
||||
Führen Sie dann den folgenden Befehl im Verzeichnis der compose-Datei aus, um den Container zu starten:
|
||||
|
||||
```sh
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### Manuelle Installation
|
||||
|
||||
Klonen Sie dieses Repository und installieren Sie die Abhängigkeiten:
|
||||
|
||||
```sh
|
||||
git clone https://forgejo.jk-effects.cloud/Ehrenamt/member-administration-ui.git
|
||||
cd member-administration-ui
|
||||
npm install
|
||||
npm run build
|
||||
npm run start
|
||||
```
|
||||
|
||||
### Konfiguration
|
||||
|
||||
Ein eigenes favicon und Logo kann über ein volume ausgetauscht werden.
|
||||
|
||||
## Einrichtung
|
||||
|
||||
1. **Admin Benutzer erstellen**: Erstellen Sie einen Admin Benutzer unter dem Pfad [/setup](https://ff-admin-demo.jk-effects.cloud/setup), um auf die Migliederverwaltung Zugriff zu erhalten. Nach der Erstellung des ersten Benutzers wird der Pfad automatisch geblockt.
|
||||
|
||||
2. **Rollen und Berechtigungen**: Unter `Benutzer > Rollen` können die Rollen und Berechtigungen für die Benutzer erstellt und angepasst werden.
|
||||
|
||||
3. **Nutzer einladen**: Unter `Benutzer > Benutzer` können weitere Nutzer eingeladen werden. Diese erhalten dann eine E-Mail mit einem Link, um ein TOTP zu erhalten.
|
||||
|
||||
## Fragen und Wünsche
|
||||
|
||||
Bei Fragen, Anregungen oder Wünschen können Sie sich gerne melden.\
|
||||
Wir freuen uns über Ihr Feedback und helfen Ihnen gerne weiter.\
|
||||
Schreiben Sie dafür eine Mail an julian.krauser@jk-effects.com.
|
||||
|
|
BIN
demo-totp-qrcode.png
Normal file
BIN
demo-totp-qrcode.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
|
@ -1,22 +1,23 @@
|
|||
<template>
|
||||
<footer
|
||||
v-if="authCheck && (routeName.includes('admin') || routeName.includes('account'))"
|
||||
v-if="authCheck && (routeName.includes('admin-') || routeName.includes('account-'))"
|
||||
class="md:hidden flex flex-row h-16 min-h-16 justify-center md:justify-normal p-1 bg-white"
|
||||
>
|
||||
<div class="w-full flex flex-row gap-2 h-full align-middle">
|
||||
<TopLevelLink
|
||||
v-if="routeName.includes('admin')"
|
||||
v-if="routeName.includes('admin-')"
|
||||
v-for="item in topLevel"
|
||||
:key="item.key"
|
||||
:link="item"
|
||||
:disableSubLink="true"
|
||||
/>
|
||||
<TopLevelLink v-else :link="{ key: 'club', title: 'Zur Verwaltung' }" :disableSubLink="true" />
|
||||
<TopLevelLink v-else :link="{ key: 'club', title: 'Zur Verwaltung', levelDefault: '' }" :disableSubLink="true" />
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import { mapState } from "pinia";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { useNavigationStore } from "@/stores/admin/navigation";
|
||||
|
@ -24,7 +25,6 @@ import TopLevelLink from "./admin/TopLevelLink.vue";
|
|||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
export default defineComponent({
|
||||
computed: {
|
||||
...mapState(useAuthStore, ["authCheck"]),
|
||||
|
|
|
@ -6,10 +6,10 @@
|
|||
</RouterLink>
|
||||
<div class="flex flex-row gap-2 items-center">
|
||||
<div v-if="authCheck" class="hidden md:flex flex-row gap-2 h-full align-middle">
|
||||
<TopLevelLink v-if="routeName.includes('admin')" v-for="item in topLevel" :key="item.key" :link="item" />
|
||||
<TopLevelLink v-if="routeName.includes('admin-')" v-for="item in topLevel" :key="item.key" :link="item" />
|
||||
<TopLevelLink
|
||||
v-else-if="routeName.includes('account')"
|
||||
:link="{ key: 'club', title: 'Zur Verwaltung' }"
|
||||
v-else-if="routeName.includes('account-')"
|
||||
:link="{ key: 'club', title: 'Zur Verwaltung', levelDefault: '' }"
|
||||
:disable-sub-link="true"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -33,9 +33,9 @@ export default defineComponent({
|
|||
},
|
||||
methods: {
|
||||
copyToClipboard() {
|
||||
navigator.clipboard.writeText(this.otp ?? "");
|
||||
navigator.clipboard.writeText(this.copyText ?? "");
|
||||
this.copySuccess = true;
|
||||
this.timputCopy = setTimeout(() => {
|
||||
this.timeoutCopy = setTimeout(() => {
|
||||
this.copySuccess = false;
|
||||
}, 2000);
|
||||
},
|
||||
|
|
|
@ -95,7 +95,7 @@ import { mapState, mapActions } from "pinia";
|
|||
import { EyeIcon, PencilIcon, PlusIcon, TrashIcon } from "@heroicons/vue/24/outline";
|
||||
import { useAbilityStore } from "@/stores/ability";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
import isEqual from "lodash.isEqual";
|
||||
import isEqual from "lodash.isequal";
|
||||
import Spinner from "@/components/Spinner.vue";
|
||||
import SuccessCheckmark from "@/components/SuccessCheckmark.vue";
|
||||
import FailureXMark from "@/components/FailureXMark.vue";
|
||||
|
|
|
@ -24,7 +24,7 @@ export default defineComponent({
|
|||
default: "LINK",
|
||||
},
|
||||
link: {
|
||||
type: [String, Object as PropType<{ name: string }>],
|
||||
type: Object as PropType<string | { name: string }>,
|
||||
default: "/",
|
||||
},
|
||||
active: {
|
||||
|
|
|
@ -18,13 +18,13 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import { RouterLink } from "vue-router";
|
||||
import { useNavigationStore, type topLevelNavigationModel } from "@/stores/admin/navigation";
|
||||
import { mapState } from "pinia";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import { RouterLink } from "vue-router";
|
||||
export default defineComponent({
|
||||
props: {
|
||||
link: {
|
||||
|
|
|
@ -94,7 +94,7 @@
|
|||
id="startdate"
|
||||
required
|
||||
:value="data.start"
|
||||
@change="($event) => ($refs.enddate.max = ($event.target as HTMLInputElement).value)"
|
||||
@change="($event) => (($refs.enddate as HTMLInputElement).max = ($event.target as HTMLInputElement).value)"
|
||||
/>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
|
|
|
@ -89,7 +89,9 @@
|
|||
@change="
|
||||
($event) => {
|
||||
calendar!.starttime = new Date(($event.target as HTMLInputElement).value).toISOString();
|
||||
$refs.endtime.min = formatForDateTimeLocalInput(($event.target as HTMLInputElement).value);
|
||||
($refs.endtime as HTMLInputElement).min = formatForDateTimeLocalInput(
|
||||
($event.target as HTMLInputElement).value
|
||||
);
|
||||
}
|
||||
"
|
||||
/>
|
||||
|
@ -122,7 +124,7 @@
|
|||
@change="
|
||||
($event) => {
|
||||
calendar!.starttime = new Date(($event.target as HTMLInputElement).value).toISOString();
|
||||
$refs.enddate.min = formatForDateInput(($event.target as HTMLInputElement).value);
|
||||
($refs.enddate as HTMLInputElement).min = formatForDateInput(($event.target as HTMLInputElement).value);
|
||||
}
|
||||
"
|
||||
/>
|
||||
|
@ -194,7 +196,7 @@ import { CheckIcon, ChevronUpDownIcon, TrashIcon } from "@heroicons/vue/20/solid
|
|||
import { useCalendarTypeStore } from "@/stores/admin/calendarType";
|
||||
import type { CalendarTypeViewModel } from "@/viewmodels/admin/calendarType.models";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
import isEqual from "lodash.isEqual";
|
||||
import isEqual from "lodash.isequal";
|
||||
import { useAbilityStore } from "@/stores/ability";
|
||||
</script>
|
||||
|
||||
|
|
|
@ -105,7 +105,7 @@ import type {
|
|||
UpdateMemberAwardViewModel,
|
||||
} from "@/viewmodels/admin/memberAward.models";
|
||||
import { useMemberAwardStore } from "@/stores/admin/memberAward";
|
||||
import isEqual from "lodash.isEqual";
|
||||
import isEqual from "lodash.isequal";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
</script>
|
||||
|
||||
|
|
|
@ -77,7 +77,7 @@ import type {
|
|||
CommunicationViewModel,
|
||||
UpdateCommunicationViewModel,
|
||||
} from "@/viewmodels/admin/communication.models";
|
||||
import isEqual from "lodash.isEqual";
|
||||
import isEqual from "lodash.isequal";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
</script>
|
||||
|
||||
|
|
|
@ -111,7 +111,7 @@ import type {
|
|||
UpdateMemberExecutivePositionViewModel,
|
||||
} from "@/viewmodels/admin/memberExecutivePosition.models";
|
||||
import { useMemberExecutivePositionStore } from "@/stores/admin/memberExecutivePosition";
|
||||
import isEqual from "lodash.isEqual";
|
||||
import isEqual from "lodash.isequal";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
</script>
|
||||
|
||||
|
|
|
@ -112,7 +112,7 @@ import type {
|
|||
UpdateMemberQualificationViewModel,
|
||||
} from "@/viewmodels/admin/memberQualification.models";
|
||||
import { useMemberQualificationStore } from "@/stores/admin/memberQualification";
|
||||
import isEqual from "lodash.isEqual";
|
||||
import isEqual from "lodash.isequal";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
</script>
|
||||
|
||||
|
|
|
@ -112,7 +112,7 @@ import type {
|
|||
UpdateMembershipViewModel,
|
||||
} from "@/viewmodels/admin/membership.models";
|
||||
import { useMembershipStore } from "@/stores/admin/membership";
|
||||
import isEqual from "lodash.isEqual";
|
||||
import isEqual from "lodash.isequal";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
</script>
|
||||
|
||||
|
|
|
@ -28,7 +28,11 @@
|
|||
|
||||
<div class="flex flex-row justify-end">
|
||||
<div class="flex flex-row gap-4 py-2">
|
||||
<button primary-outline @click="closeModal" :disabled="status != null && status?.status != 'failed'">
|
||||
<button
|
||||
primary-outline
|
||||
@click="closeModal"
|
||||
:disabled="status != null && status != 'loading' && status?.status != 'failed'"
|
||||
>
|
||||
abbrechen
|
||||
</button>
|
||||
</div>
|
||||
|
@ -47,7 +51,6 @@ import { useCalendarTypeStore } from "@/stores/admin/calendarType";
|
|||
import type { CreateCalendarTypeViewModel } from "@/viewmodels/admin/calendarType.models";
|
||||
import { Listbox, ListboxButton, ListboxOptions, ListboxOption, ListboxLabel } from "@headlessui/vue";
|
||||
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
|
||||
import type { CalendarFieldType } from "@/types/fieldTypes";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
|
@ -56,7 +59,6 @@ export default defineComponent({
|
|||
return {
|
||||
status: null as null | "loading" | { status: "success" | "failed"; reason?: string },
|
||||
timeout: undefined as any,
|
||||
selectedFields: [] as Array<CalendarFieldType>,
|
||||
};
|
||||
},
|
||||
beforeUnmount() {
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
<script setup lang="ts">
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import { mapState, mapActions } from "pinia";
|
||||
import type { InviteUserModal } from "@/viewmodels/admin/invite.models";
|
||||
import type { InviteViewModel } from "@/viewmodels/admin/invite.models";
|
||||
import { PencilIcon, UserGroupIcon, WrenchScrewdriverIcon, TrashIcon } from "@heroicons/vue/24/outline";
|
||||
import { useAbilityStore } from "@/stores/ability";
|
||||
import { useInviteStore } from "@/stores/admin/invite";
|
||||
|
@ -33,7 +33,7 @@ import { useInviteStore } from "@/stores/admin/invite";
|
|||
<script lang="ts">
|
||||
export default defineComponent({
|
||||
props: {
|
||||
invite: { type: Object as PropType<InviteUserModal>, default: {} },
|
||||
invite: { type: Object as PropType<InviteViewModel>, default: {} },
|
||||
},
|
||||
computed: {
|
||||
...mapState(useAbilityStore, ["can"]),
|
||||
|
|
|
@ -57,7 +57,7 @@ body {
|
|||
}
|
||||
|
||||
/*:not([headlessui]):not([id*="headlessui"]):not([class*="headlessui"])*/
|
||||
button:not([class*="ql"] *):not([class*="fc"]),
|
||||
button:not([class*="ql"] *):not([class*="fc"]):not([id*="headlessui-combobox"]),
|
||||
a[button] {
|
||||
@apply relative box-border h-10 w-full flex justify-center py-2 px-4 text-sm font-medium rounded-md focus:outline-none focus:ring-0;
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import router from "./router";
|
|||
let devMode = process.env.NODE_ENV === "development";
|
||||
|
||||
const http = axios.create({
|
||||
baseURL: devMode ? "http://localhost:5000" : process.env.SERVER_ADDRESS,
|
||||
baseURL: (devMode ? "http://localhost:5000" : "") + "/api",
|
||||
headers: {
|
||||
"Cache-Control": "no-cache",
|
||||
Pragma: "no-cache",
|
||||
|
|
|
@ -4,7 +4,7 @@ import { http } from "@/serverCom";
|
|||
import type { AxiosResponse } from "axios";
|
||||
import type { ProtocolViewModel } from "@/viewmodels/admin/protocol.models";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
import isEqual from "lodash.isEqual";
|
||||
import isEqual from "lodash.isequal";
|
||||
import difference from "lodash.difference";
|
||||
|
||||
export const useProtocolStore = defineStore("protocol", {
|
||||
|
|
|
@ -6,7 +6,7 @@ import type {
|
|||
} from "../../viewmodels/admin/protocolAgenda.models";
|
||||
import { useProtocolStore } from "./protocol";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
import isEqual from "lodash.isEqual";
|
||||
import isEqual from "lodash.isequal";
|
||||
import differenceWith from "lodash.differencewith";
|
||||
|
||||
export const useProtocolAgendaStore = defineStore("protocolAgenda", {
|
||||
|
|
|
@ -7,7 +7,7 @@ import type {
|
|||
} from "../../viewmodels/admin/protocolDecision.models";
|
||||
import { useProtocolStore } from "./protocol";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
import isEqual from "lodash.isEqual";
|
||||
import isEqual from "lodash.isequal";
|
||||
import differenceWith from "lodash.differencewith";
|
||||
|
||||
export const useProtocolDecisionStore = defineStore("protocolDecision", {
|
||||
|
|
|
@ -7,7 +7,7 @@ import type {
|
|||
} from "../../viewmodels/admin/protocolPresence.models";
|
||||
import { useProtocolStore } from "./protocol";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
import isEqual from "lodash.isEqual";
|
||||
import isEqual from "lodash.isequal";
|
||||
|
||||
export const useProtocolPresenceStore = defineStore("protocolPresence", {
|
||||
state: () => {
|
||||
|
|
|
@ -7,7 +7,7 @@ import type {
|
|||
} from "../../viewmodels/admin/protocolVoting.models";
|
||||
import { useProtocolStore } from "./protocol";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
import isEqual from "lodash.isEqual";
|
||||
import isEqual from "lodash.isequal";
|
||||
import differenceWith from "lodash.differencewith";
|
||||
|
||||
export const useProtocolVotingStore = defineStore("protocolVoting", {
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
}"
|
||||
>
|
||||
<span class="block truncate" :class="{ 'font-medium': selected, 'font-normal': !selected }">
|
||||
{{ user.firstname }} {{ user.lastname }} {{ user.nameaffix }}
|
||||
{{ user.firstname }} {{ user.lastname }}
|
||||
</span>
|
||||
<span
|
||||
v-if="selected"
|
||||
|
@ -124,15 +124,6 @@ export default defineComponent({
|
|||
.includes(this.query.toLowerCase().replace(/\s+/g, ""))
|
||||
);
|
||||
},
|
||||
sorted(): Array<MemberViewModel> {
|
||||
return this.selected.sort((a, b) => {
|
||||
if (a.lastname < b.lastname) return -1;
|
||||
if (a.lastname > b.lastname) return 1;
|
||||
if (a.firstname < b.firstname) return -1;
|
||||
if (a.firstname > b.firstname) return 1;
|
||||
return 0;
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetchUsers();
|
||||
|
|
|
@ -48,12 +48,13 @@
|
|||
<script setup lang="ts">
|
||||
import { defineComponent, markRaw, defineAsyncComponent } from "vue";
|
||||
import { mapActions, mapState } from "pinia";
|
||||
import type { UserViewModel } from "@/viewmodels/admin/user.models";
|
||||
import MainTemplate from "@/templates/Main.vue";
|
||||
import Spinner from "@/components/Spinner.vue";
|
||||
import SuccessCheckmark from "@/components/SuccessCheckmark.vue";
|
||||
import FailureXMark from "@/components/FailureXMark.vue";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
import isEqual from "lodash.isEqual";
|
||||
import isEqual from "lodash.isequal";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
|
|
|
@ -96,7 +96,7 @@ import FailureXMark from "@/components/FailureXMark.vue";
|
|||
import { Listbox, ListboxButton, ListboxOptions, ListboxOption, ListboxLabel } from "@headlessui/vue";
|
||||
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
import isEqual from "lodash.isEqual";
|
||||
import isEqual from "lodash.isequal";
|
||||
import { Salutation } from "@/enums/salutation";
|
||||
</script>
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ import FailureXMark from "@/components/FailureXMark.vue";
|
|||
import { RouterLink } from "vue-router";
|
||||
import type { AwardViewModel, UpdateAwardViewModel } from "@/viewmodels/admin/award.models";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
import isEqual from "lodash.isEqual";
|
||||
import isEqual from "lodash.isequal";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
|
|
|
@ -57,7 +57,7 @@ import { RouterLink } from "vue-router";
|
|||
import type { CalendarTypeViewModel, UpdateCalendarTypeViewModel } from "@/viewmodels/admin/calendarType.models";
|
||||
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
import isEqual from "lodash.isEqual";
|
||||
import isEqual from "lodash.isequal";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
|
|
|
@ -15,12 +15,7 @@
|
|||
/>
|
||||
</div>
|
||||
<div class="flex flex-row gap-4">
|
||||
<button
|
||||
v-if="can('create', 'settings', 'communication_type')"
|
||||
primary
|
||||
class="!w-fit"
|
||||
@click="openCreateModal"
|
||||
>
|
||||
<button v-if="can('create', 'settings', 'communication')" primary class="!w-fit" @click="openCreateModal">
|
||||
Kommunikationsart erstellen
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -97,7 +97,7 @@ import type {
|
|||
import { Listbox, ListboxButton, ListboxOptions, ListboxOption, ListboxLabel } from "@headlessui/vue";
|
||||
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
import isEqual from "lodash.isEqual";
|
||||
import isEqual from "lodash.isequal";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
|
|
|
@ -50,7 +50,7 @@ import type {
|
|||
UpdateExecutivePositionViewModel,
|
||||
} from "@/viewmodels/admin/executivePosition.models";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
import isEqual from "lodash.isEqual";
|
||||
import isEqual from "lodash.isequal";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
|
|
|
@ -50,7 +50,7 @@ import type {
|
|||
MembershipStatusViewModel,
|
||||
} from "@/viewmodels/admin/membershipStatus.models";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
import isEqual from "lodash.isEqual";
|
||||
import isEqual from "lodash.isequal";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
|
|
|
@ -51,7 +51,7 @@ import FailureXMark from "@/components/FailureXMark.vue";
|
|||
import { RouterLink } from "vue-router";
|
||||
import type { UpdateQualificationViewModel, QualificationViewModel } from "@/viewmodels/admin/qualification.models";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
import isEqual from "lodash.isEqual";
|
||||
import isEqual from "lodash.isequal";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
|
|
|
@ -46,7 +46,7 @@ import SuccessCheckmark from "@/components/SuccessCheckmark.vue";
|
|||
import FailureXMark from "@/components/FailureXMark.vue";
|
||||
import { RouterLink } from "vue-router";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
import isEqual from "lodash.isEqual";
|
||||
import isEqual from "lodash.isequal";
|
||||
import type { RoleViewModel } from "@/viewmodels/admin/role.models";
|
||||
</script>
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ import { RouterLink } from "vue-router";
|
|||
import { useUserStore } from "@/stores/admin/user";
|
||||
import type { UpdateUserViewModel, UserViewModel } from "@/viewmodels/admin/user.models";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
import isEqual from "lodash.isEqual";
|
||||
import isEqual from "lodash.isequal";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
|
|
|
@ -66,7 +66,7 @@ import FailureXMark from "@/components/FailureXMark.vue";
|
|||
import { XMarkIcon, PlusIcon } from "@heroicons/vue/24/outline";
|
||||
import type { UserViewModel } from "@/viewmodels/admin/user.models";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
import isEqual from "lodash.isEqual";
|
||||
import isEqual from "lodash.isequal";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
|
|
Loading…
Reference in a new issue