group admin init

This commit is contained in:
hay-kot 2021-03-12 22:44:15 -08:00
commit f5a193f3b6
11 changed files with 422 additions and 339 deletions

View file

@ -0,0 +1,123 @@
<template>
<div>
<Confirmation
ref="deleteGroupConfirm"
title="Confirm Group Deletion"
:message="`Are you sure you want to delete <b>${group.name}<b/>`"
icon="mdi-alert"
@confirm="deleteGroup"
:width="450"
@close="closeGroupDelete"
/>
<v-card class="ma-auto" tile min-height="325px">
<v-list dense>
<v-card-title class="py-1">{{ group.name }}</v-card-title>
<v-divider></v-divider>
<v-subheader>Group ID: {{ group.id }}</v-subheader>
<v-list-item-group color="primary">
<v-list-item v-for="property in groupProps" :key="property.text">
<v-list-item-icon>
<v-icon> {{ property.icon || "mdi-account" }} </v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title class="pl-4 flex row justify-space-between">
<div>{{ property.text }}</div>
<div>{{ property.value }}</div>
</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list-item-group>
</v-list>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
small
color="error"
@click="confirmDelete"
:disabled="ableToDelete"
>
Delete
</v-btn>
<!-- Coming Soon! -->
<v-btn small color="success" disabled>
Edit
</v-btn>
</v-card-actions>
</v-card>
</div>
</template>
<script>
const RENDER_EVENT = "update";
import Confirmation from "@/components/UI/Confirmation";
import api from "@/api";
export default {
components: { Confirmation },
props: {
group: {
default: {
name: "DEFAULT_NAME",
id: 1,
users: [],
mealplans: [],
categories: [],
webhookUrls: [],
webhookTime: "00:00",
webhookEnable: false,
},
},
},
data() {
return {
groupProps: {},
};
},
computed: {
ableToDelete() {
return this.group.users.length >= 1 ? true : false;
},
},
mounted() {
this.buildData();
},
methods: {
confirmDelete() {
this.$refs.deleteGroupConfirm.open();
},
async deleteGroup() {
await api.groups.delete(this.group.id);
this.$emit(RENDER_EVENT);
},
closeGroupDelete() {
console.log("Close Delete");
},
buildData() {
this.groupProps = [
{
text: "Total Users",
icon: "mdi-account",
value: this.group.users.length,
},
{
text: "Total MealPlans",
icon: "mdi-food",
value: this.group.mealplans.length,
},
{
text: "Webhooks Enabled",
icon: "mdi-webhook",
value: this.group.webhookEnable ? "True" : "False",
},
{
text: "Webhook Time",
icon: "mdi-clock-outline",
value: this.group.webhookTime,
},
];
},
},
};
</script>
<style scoped>
</style>

View file

@ -0,0 +1,120 @@
<template>
<div>
<v-card outlined class="mt-n1">
<v-card-actions>
<v-spacer></v-spacer>
<div width="100px">
<v-text-field
v-model="filter"
clearable
class="mr-2 pt-0"
append-icon="mdi-filter"
label="Filter"
single-line
hide-details
></v-text-field>
</div>
<v-dialog v-model="groupDialog" max-width="400">
<template v-slot:activator="{ on, attrs }">
<v-btn
class="mx-2"
small
color="success"
dark
v-bind="attrs"
v-on="on"
>
Create Group
</v-btn>
</template>
<v-card>
<v-app-bar dark dense color="primary">
<v-icon left>
mdi-account-group
</v-icon>
<v-toolbar-title class="headline">
Create Group
</v-toolbar-title>
<v-spacer></v-spacer>
</v-app-bar>
<v-card-text>
<v-form ref="newGroup">
<v-text-field
v-model="newGroupName"
label="Group Name"
:rules="[existsRule]"
></v-text-field>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="grey" text @click="groupDialog = false">
Cancel
</v-btn>
<v-btn color="primary" @click="createGroup">
Create
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-card-actions>
<v-card-text>
<v-row>
<v-col
:sm="6"
:md="6"
:lg="3"
:xl="3"
v-for="group in groups"
:key="group.id"
>
<GroupCard
:group="group"
@update="$store.dispatch('requestAllGroups')"
/>
</v-col>
</v-row>
</v-card-text>
</v-card>
</div>
</template>
<script>
import { validators } from "@/mixins/validators";
import api from "@/api";
import GroupCard from "@/components/Admin/ManageUsers/GroupCard";
export default {
components: { GroupCard },
mixins: [validators],
data() {
return {
filter: "",
groupDialog: false,
newGroupName: "",
};
},
computed: {
groups() {
return this.$store.getters.getGroups;
},
},
methods: {
async createGroup() {
this.groupLoading = true;
let response = await api.groups.create(this.newGroupName);
if (response.created) {
this.groupLoading = false;
this.groupDialog = false;
this.$store.dispatch("requestAllGroups");
}
},
},
};
</script>
<style>
</style>

View file

@ -1,256 +0,0 @@
<template>
<v-card outlined class="mt-n1">
<Confirmation
ref="deleteUserDialog"
title="Confirm User Deletion"
:message="
`Are you sure you want to delete the user <b>${activeName} ID: ${activeId}<b/>`
"
icon="mdi-alert"
@confirm="deleteUser"
:width="450"
@close="closeDelete"
/>
<v-toolbar flat>
<v-icon large color="accent" class="mr-1">
mdi-account-group
</v-icon>
<v-toolbar-title class="headine">
User Groups
</v-toolbar-title>
<div>
<v-select class="mt-7 ml-5" dense flat solo> </v-select>
</div>
<v-spacer> </v-spacer>
<v-dialog v-model="dialog" max-width="600px">
<template v-slot:activator="{ on, attrs }">
<v-btn small color="success" dark v-bind="attrs" v-on="on">
Create Group
</v-btn>
</template>
<v-card>
<v-app-bar dark dense color="primary">
<v-icon left>
mdi-account
</v-icon>
<v-toolbar-title class="headline">
{{ formTitle }}
</v-toolbar-title>
<v-spacer></v-spacer>
<v-toolbar-title class="headline">
User ID: {{ editedItem.id }}
</v-toolbar-title>
</v-app-bar>
<v-card-text>
<v-form ref="newUser">
<v-row>
<v-col cols="12" sm="12" md="6">
<v-text-field
v-model="editedItem.fullName"
label="Full Name"
:rules="[existsRule]"
validate-on-blur
></v-text-field>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
v-model="editedItem.email"
label="Email"
:rules="[existsRule, emailRule]"
validate-on-blur
></v-text-field>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
v-model="editedItem.group"
label="Group Group"
></v-text-field>
</v-col>
<v-col cols="12" sm="12" md="6" v-if="showPassword">
<v-text-field
v-model="editedItem.password"
label="User Password"
:rules="[existsRule, minRule]"
></v-text-field>
</v-col>
<v-col cols="12" sm="12" md="3">
<v-switch v-model="editedItem.admin" label="Admin"></v-switch>
</v-col>
</v-row>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="grey" text @click="close">
Cancel
</v-btn>
<v-btn color="primary" @click="save">
Save
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-toolbar>
<v-divider></v-divider>
<v-card-text>
<v-data-table :headers="headers" :items="users" sort-by="calories">
<template v-slot:item.actions="{ item }">
<v-btn class="mr-1" small color="error" @click="deleteItem(item)">
<v-icon small left>
mdi-delete
</v-icon>
Delete
</v-btn>
<v-btn small color="success" @click="editItem(item)">
<v-icon small left class="mr-2">
mdi-pencil
</v-icon>
Edit
</v-btn>
</template>
<template v-slot:item.admin="{ item }">
{{ item.admin ? "Admin" : "User" }}
</template>
<template v-slot:no-data>
<v-btn color="primary" @click="initialize">
Reset
</v-btn>
</template>
</v-data-table>
</v-card-text>
</v-card>
</template>
<script>
import Confirmation from "@/components/UI/Confirmation";
import api from "@/api";
import { validators } from "@/mixins/validators";
export default {
components: { Confirmation },
mixins: [validators],
data: () => ({
dialog: false,
activeId: null,
activeName: null,
headers: [
{
text: "User IDs",
align: "start",
sortable: false,
value: "id",
},
{ text: "Full Name", value: "fullName" },
{ text: "Email", value: "email" },
{ text: "Group", value: "group" },
{ text: "Admin", value: "admin" },
{ text: "", value: "actions", sortable: false, align: "center" },
],
users: [],
editedIndex: -1,
editedItem: {
id: 0,
fullName: "",
password: "",
email: "",
group: "",
admin: false,
},
defaultItem: {
id: 0,
fullName: "",
password: "",
email: "",
group: "",
admin: false,
},
}),
computed: {
formTitle() {
return this.editedIndex === -1 ? "New User" : "Edit User";
},
showPassword() {
return this.editedIndex === -1 ? true : false;
},
},
watch: {
dialog(val) {
val || this.close();
},
dialogDelete(val) {
val || this.closeDelete();
},
},
created() {
this.initialize();
},
methods: {
async initialize() {
this.users = await api.users.allUsers();
},
async deleteUser() {
await api.users.delete(this.activeId);
this.initialize();
},
editItem(item) {
this.editedIndex = this.users.indexOf(item);
this.editedItem = Object.assign({}, item);
this.dialog = true;
},
deleteItem(item) {
this.activeId = item.id;
this.activeName = item.fullName;
this.editedIndex = this.users.indexOf(item);
this.editedItem = Object.assign({}, item);
this.$refs.deleteUserDialog.open();
},
deleteItemConfirm() {
this.users.splice(this.editedIndex, 1);
this.closeDelete();
},
close() {
this.dialog = false;
this.$nextTick(() => {
this.editedItem = Object.assign({}, this.defaultItem);
this.editedIndex = -1;
});
},
closeDelete() {
this.dialogDelete = false;
this.$nextTick(() => {
this.editedItem = Object.assign({}, this.defaultItem);
this.editedIndex = -1;
});
},
async save() {
if (this.editedIndex > -1) {
api.users.update(this.editedItem);
this.close();
} else if (this.$refs.newUser.validate()) {
api.users.create(this.editedItem);
this.close();
}
await this.initialize();
},
},
};
</script>
<style>
</style>

View file

@ -68,7 +68,7 @@
</v-dialog>
</v-toolbar>
<v-divider></v-divider>
<v-card-text>
<v-data-table :headers="headers" :items="links" sort-by="calories">
<template v-slot:item.token="{ item }">

View file

@ -12,35 +12,18 @@
@close="closeDelete"
/>
<v-toolbar flat>
<v-icon large color="accent" class="mr-1">
mdi-account
</v-icon>
<v-toolbar-title class="headine">
Users
</v-toolbar-title>
<v-spacer> </v-spacer>
<v-text-field
v-model="search"
append-icon="mdi-filter"
label="Filter"
single-line
hide-details
></v-text-field>
<v-dialog>
<template v-slot:activator="{ on, attrs }">
<v-btn
class="mx-2"
small
color="success"
dark
v-bind="attrs"
v-on="on"
>
Create Group
</v-btn>
</template>
</v-dialog>
<div width="100px">
<v-text-field
v-model="search"
class="mr-2"
append-icon="mdi-filter"
label="Filter"
single-line
hide-details
></v-text-field>
</div>
<v-dialog v-model="dialog" max-width="600px">
<template v-slot:activator="{ on, attrs }">
<v-btn small color="success" dark v-bind="attrs" v-on="on">
@ -83,11 +66,12 @@
></v-text-field>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field
<v-select
dense
v-model="editedItem.group"
label="Group Group"
></v-text-field>
:items="existingGroups"
label="User Group"
></v-select>
</v-col>
<v-col cols="12" sm="12" md="6" v-if="showPassword">
<v-text-field
@ -203,6 +187,9 @@ export default {
showPassword() {
return this.editedIndex === -1 ? true : false;
},
existingGroups() {
return this.$store.getters.getGroupNames;
},
},
watch: {

View file

@ -0,0 +1,53 @@
<template>
<div>
<v-dialog v-model="dialog" :width="modalWidth + 'px'">
<v-app-bar dark :color="color" class="mt-n1 mb-2">
<v-icon large left v-if="!loading">
{{ titleIcon }}
</v-icon>
<v-progress-circular
v-else
indeterminate
color="white"
large
class="mr-2"
>
</v-progress-circular>
<v-toolbar-title class="headline"> {{ title }} </v-toolbar-title>
<v-spacer></v-spacer>
</v-app-bar>
</v-dialog>
</div>
</template>
<script>
export default {
props: {
color: {
default: "primary",
},
title: {
default: "Modal Title",
},
titleIcon: {
default: "mdi-account",
},
modalWidth: {
default: "500",
},
},
data() {
return {
dialog: false,
};
},
methods: {
open() {
this.dialog = true;
},
},
};
</script>
<style scoped>
</style>

View file

@ -8,10 +8,10 @@
@keydown.esc="cancel"
>
<v-card>
<v-toolbar v-if="Boolean(title)" :color="color" dense flat dark>
<v-app-bar v-if="Boolean(title)" :color="color" dense flat dark>
<v-icon v-if="Boolean(icon)" left> {{ icon }}</v-icon>
<v-toolbar-title v-text="title" />
</v-toolbar>
</v-app-bar>
<v-card-text
v-show="!!message"

View file

@ -34,7 +34,7 @@
<TheSignUpTable />
</v-tab-item>
<v-tab-item>
<TheGroupTable />
<GroupDashboard />
</v-tab-item>
</v-tabs-items>
</v-card>
@ -43,15 +43,18 @@
<script>
import TheUserTable from "@/components/Admin/ManageUsers/TheUserTable";
import TheGroupTable from "@/components/Admin/ManageUsers/TheGroupTable";
import GroupDashboard from "@/components/Admin/ManageUsers/GroupDashboard";
import TheSignUpTable from "@/components/Admin/ManageUsers/TheSignUpTable";
export default {
components: { TheUserTable, TheGroupTable, TheSignUpTable },
components: { TheUserTable, GroupDashboard, TheSignUpTable },
data() {
return {
tab: 0,
};
},
mounted() {
this.$store.dispatch("requestAllGroups");
},
};
</script>

View file

@ -13,13 +13,17 @@
outlined
:flat="isFlat"
elavation="0"
v-model="planCategories"
v-model="groupSettings.categories"
:items="categories"
item-text="name"
item-value="name"
return-object
multiple
chips
:hint="$t('meal-plan.only-recipes-with-these-categories-will-be-used-in-meal-plans')"
:hint="
$t(
'meal-plan.only-recipes-with-these-categories-will-be-used-in-meal-plans'
)
"
class="mt-2"
persistent-hint
>
@ -50,12 +54,15 @@
"settings.webhooks.the-urls-listed-below-will-recieve-webhooks-containing-the-recipe-data-for-the-meal-plan-on-its-scheduled-day-currently-webhooks-will-execute-at"
)
}}
<strong>{{ time }}</strong>
<strong>{{ groupSettings.webhookTime }}</strong>
</p>
<v-row dense align="center">
<v-col cols="12" md="2" sm="5">
<v-switch v-model="enabled" :label="$t('general.enabled')"></v-switch>
<v-switch
v-model="groupSettings.webhookEnable"
:label="$t('general.enabled')"
></v-switch>
</v-col>
<v-col cols="12" md="3" sm="5">
<TimePickerDialog @save-time="saveTime" />
@ -68,7 +75,12 @@
</v-col>
</v-row>
<v-row v-for="(url, index) in webhooks" :key="index" align="center" dense>
<v-row
v-for="(url, index) in groupSettings.webhookUrls"
:key="index"
align="center"
dense
>
<v-col cols="1">
<v-btn icon color="error" @click="removeWebhook(index)">
<v-icon>mdi-minus</v-icon>
@ -76,7 +88,7 @@
</v-col>
<v-col>
<v-text-field
v-model="webhooks[index]"
v-model="groupSettings.webhookUrls[index]"
:label="$t('settings.webhooks.webhook-url')"
></v-text-field>
</v-col>
@ -87,7 +99,7 @@
<v-icon>mdi-plus</v-icon>
</v-btn>
<v-spacer></v-spacer>
<v-btn color="success" @click="saveWebhooks" class="mr-2 mb-1">
<v-btn color="success" @click="saveGroupSettings" class="mr-2 mb-1">
<v-icon left> mdi-content-save </v-icon>
{{ $t("general.save") }}
</v-btn>
@ -104,14 +116,19 @@ export default {
},
data() {
return {
name: "main",
webhooks: [],
enabled: false,
time: "",
planCategories: [],
groupSettings: {
name: "home",
id: 1,
mealplans: [],
categories: [],
webhookUrls: [],
webhookTime: "00:00",
webhookEnable: false,
},
};
},
mounted() {
async mounted() {
await this.$store.dispatch("requestCurrentGroup");
this.getSiteSettings();
},
computed: {
@ -119,44 +136,39 @@ export default {
return this.$store.getters.getCategories;
},
isFlat() {
return this.planCategories ? true : false;
return this.groupSettings.categories >= 1 ? true : false;
},
},
methods: {
saveTime(value) {
this.time = value;
this.groupSettings.webhookTime = value;
},
async getSiteSettings() {
let settings = await api.settings.requestAll();
this.webhooks = settings.webhooks.webhookURLs;
this.name = settings.name;
this.time = settings.webhooks.webhookTime;
this.enabled = settings.webhooks.enabled;
this.planCategories = settings.planCategories;
getSiteSettings() {
let settings = this.$store.getters.getCurrentGroup;
this.groupSettings.name = settings.name;
this.groupSettings.id = settings.id;
this.groupSettings.categories = settings.categories;
this.groupSettings.webhookUrls = settings.webhookUrls;
this.groupSettings.webhookTime = settings.webhookTime;
this.groupSettings.webhookEnable = settings.webhookEnable;
},
addWebhook() {
this.webhooks.push(" ");
this.groupSettings.webhookUrls.push(" ");
},
removeWebhook(index) {
this.webhooks.splice(index, 1);
this.groupSettings.webhookUrls.splice(index, 1);
},
saveWebhooks() {
const body = {
name: this.name,
planCategories: this.planCategories,
webhooks: {
webhookURLs: this.webhooks,
webhookTime: this.time,
enabled: this.enabled,
},
};
api.settings.update(body);
async saveGroupSettings() {
await api.groups.update(this.groupSettings);
await this.$store.dispatch("requestCurrentGroup");
this.getSiteSettings();
},
testWebhooks() {
api.settings.testWebhooks();
},
removeCategory(index) {
this.planCategories.splice(index, 1);
this.groupSettings.categories.splice(index, 1);
},
},
};

View file

@ -1,11 +1,13 @@
<template>
<v-container>
<AdminSidebar />
<v-slide-x-transition hide-on-leave>
<router-view></router-view>
</v-slide-x-transition>
<!-- <v-footer fixed>
<v-col class="text-center" cols="12">
<div>
<v-container height="100%">
<AdminSidebar />
<v-slide-x-transition hide-on-leave>
<router-view></router-view>
</v-slide-x-transition>
</v-container>
<!-- <v-footer absolute>
<div class="flex text-center" cols="12">
{{ $t("settings.current") }}
{{ version }} |
{{ $t("settings.latest") }}
@ -21,9 +23,9 @@
>
{{ $t("settings.contribute") }}
</a>
</v-col>
</div>
</v-footer> -->
</v-container>
</div>
</template>
<script>

View file

@ -0,0 +1,39 @@
import api from "@/api";
const state = {
groups: [],
currentGroup: {},
};
const mutations = {
setGroups(state, payload) {
state.groups = payload;
},
setCurrentGroup(state, payload) {
state.currentGroup = payload;
},
};
const actions = {
async requestAllGroups({ commit }) {
const groups = await api.groups.allGroups();
commit("setGroups", groups);
},
async requestCurrentGroup({ commit }) {
const group = await api.groups.current();
commit("setCurrentGroup", group);
},
};
const getters = {
getGroups: state => state.groups,
getGroupNames: state => Array.from(state.groups, x => x.name),
getCurrentGroup: state => state.currentGroup
};
export default {
state,
mutations,
actions,
getters,
};