feature/editor-improvements (#289)

* pin editor buttons on scroll

* scaler scratch

* fix langauge assignment 1st pass

* set lang on navigate

* refactor/breakup router

* unify style for language selectro

* refactor/code-cleanup

* refactor/page specific components to page folder

* Fix time card layout issue

* fix timecard display

* update mobile cards / fix overflow errors

Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
Hayden
2021-04-21 21:52:12 -08:00
committed by GitHub
parent a5306c31c6
commit 284df44209
66 changed files with 778 additions and 664 deletions

View File

@@ -38,7 +38,7 @@
<script>
import { api } from "@/api";
import TheDownloadBtn from "@/components/UI/TheDownloadBtn";
import TheDownloadBtn from "@/components/UI/Buttons/TheDownloadBtn";
export default {
components: { TheDownloadBtn },
data() {

View File

@@ -0,0 +1,90 @@
<template>
<div>
<ImportDialog
:name="selectedName"
:date="selectedDate"
ref="import_dialog"
@import="importBackup"
@delete="deleteBackup"
/>
<v-row>
<v-col
:cols="12"
:sm="6"
:md="6"
:lg="4"
:xl="4"
v-for="backup in backups"
:key="backup.name"
>
<v-card hover outlined @click="openDialog(backup)">
<v-card-text>
<v-row align="center">
<v-col cols="2">
<v-icon large color="primary">mdi-backup-restore</v-icon>
</v-col>
<v-col cols="10">
<div class="text-truncate">
<strong>{{ backup.name }}</strong>
</div>
<div class="text-truncate">{{ readableTime(backup.date) }}</div>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-col>
</v-row>
</div>
</template>
<script>
import ImportDialog from "./ImportDialog";
import { api } from "@/api";
import utils from "@/utils";
export default {
props: {
backups: Array,
},
components: {
ImportDialog,
},
data() {
return {
selectedName: "",
selectedDate: "",
loading: false,
};
},
methods: {
openDialog(backup) {
this.selectedDate = this.readableTime(backup.date);
this.selectedName = backup.name;
this.$refs.import_dialog.open();
},
readableTime(timestamp) {
let date = new Date(timestamp);
return utils.getDateAsText(date);
},
async importBackup(data) {
this.$emit("loading");
let response = await api.backups.import(data.name, data);
let importData = response.data;
this.$emit("finished", importData);
},
deleteBackup(data) {
this.$emit("loading");
api.backups.delete(data.name);
this.selectedBackup = null;
this.backupLoading = false;
this.$emit("finished");
},
},
};
</script>
<style>
</style>

View File

@@ -0,0 +1,90 @@
<template>
<div>
<ImportDialog
:name="selectedName"
:date="selectedDate"
ref="import_dialog"
@import="importBackup"
@delete="deleteBackup"
/>
<v-row>
<v-col
:sm="6"
:md="6"
:lg="4"
:xl="4"
v-for="backup in backups"
:key="backup.name"
>
<v-card @click="openDialog(backup)">
<v-card-text>
<v-row align="center">
<v-col cols="12" sm="2">
<v-icon color="primary"> mdi-backup-restore </v-icon>
</v-col>
<v-col cols="12" sm="10">
<div>
<strong>{{ backup.name }}</strong>
</div>
<div>{{ readableTime(backup.date) }}</div>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-col>
</v-row>
</div>
</template>
<script>
import ImportDialog from "./ImportDialog";
import { api } from "@/api";
import utils from "@/utils";
export default {
props: {
backups: Array,
},
components: {
ImportDialog,
},
data() {
return {
selectedName: "",
selectedDate: "",
loading: false,
};
},
methods: {
openDialog(backup) {
this.selectedDate = this.readableTime(backup.date);
this.selectedName = backup.name;
this.$refs.import_dialog.open();
},
readableTime(timestamp) {
let date = new Date(timestamp);
return utils.getDateAsText(date);
},
async importBackup(data) {
this.$emit("loading");
let response = await api.backups.import(data.name, data);
let failed = response.data.failed;
let succesful = response.data.successful;
this.$emit("finished", succesful, failed);
},
deleteBackup(data) {
this.$emit("loading");
api.backups.delete(data.name);
this.selectedBackup = null;
this.backupLoading = false;
this.$emit("finished");
},
},
};
</script>
<style>
</style>

View File

@@ -0,0 +1,123 @@
<template>
<div class="text-center">
<v-dialog
v-model="dialog"
width="500"
:fullscreen="$vuetify.breakpoint.xsOnly"
>
<v-card>
<v-toolbar dark color="primary" v-show="$vuetify.breakpoint.xsOnly">
<v-btn icon dark @click="dialog = false">
<v-icon>mdi-close</v-icon>
</v-btn>
<v-toolbar-title></v-toolbar-title>
<v-spacer></v-spacer>
<v-toolbar-items>
<v-btn dark text @click="raiseEvent('import')">
{{ $t("general.import") }}
</v-btn>
</v-toolbar-items>
</v-toolbar>
<v-card-title> {{ name }} </v-card-title>
<v-card-subtitle class="mb-n3"> {{ date }} </v-card-subtitle>
<v-divider></v-divider>
<v-card-text>
<ImportOptions @update-options="updateOptions" class="mt-5 mb-2" />
<v-divider></v-divider>
<v-checkbox
dense
label="Remove existing entries matching imported entries"
v-model="forceImport"
></v-checkbox>
</v-card-text>
<v-divider></v-divider>
<v-card-actions>
<TheDownloadBtn :download-url="downloadUrl" />
<v-spacer></v-spacer>
<v-btn color="error" text @click="raiseEvent('delete')">
{{ $t("general.delete") }}
</v-btn>
<v-btn
color="success"
outlined
@click="raiseEvent('import')"
v-show="$vuetify.breakpoint.smAndUp"
>
{{ $t("general.import") }}
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script>
import ImportOptions from "./ImportOptions";
import TheDownloadBtn from "@/components/UI/Buttons/TheDownloadBtn.vue";
import { backupURLs } from "@/api/backup";
export default {
components: { ImportOptions, TheDownloadBtn },
props: {
name: {
default: "Backup Name",
},
date: {
default: "Backup Date",
},
},
data() {
return {
options: {
recipes: true,
settings: true,
themes: true,
users: true,
groups: true,
},
dialog: false,
forceImport: false,
rebaseImport: false,
downloading: false,
};
},
computed: {
downloadUrl() {
return backupURLs.downloadBackup(this.name);
},
},
methods: {
updateOptions(options) {
this.options = options;
},
open() {
this.dialog = true;
},
close() {
this.dialog = false;
},
raiseEvent(event) {
let eventData = {
name: this.name,
force: this.forceImport,
rebase: this.rebaseImport,
recipes: this.options.recipes,
settings: this.options.settings,
themes: this.options.themes,
users: this.options.users,
groups: this.options.groups,
};
this.close();
this.$emit(event, eventData);
},
},
};
</script>
<style>
</style>

View File

@@ -0,0 +1,67 @@
<template>
<div>
<v-checkbox
v-for="option in options"
:key="option.text"
class="mb-n4 mt-n3"
dense
:label="option.text"
v-model="option.value"
@change="emitValue()"
></v-checkbox>
</div>
</template>
<script>
const UPDATE_EVENT = "update-options";
export default {
data() {
return {
options: {
recipes: {
value: true,
text: this.$t("general.recipes"),
},
settings: {
value: true,
text: this.$t("general.settings"),
},
pages: {
value: true,
text: "Pages",
},
themes: {
value: true,
text: this.$t("general.themes"),
},
users: {
value: true,
text: this.$t("general.users"),
},
groups: {
value: true,
text: this.$t("general.groups"),
},
},
};
},
mounted() {
this.emitValue();
},
methods: {
emitValue() {
this.$emit(UPDATE_EVENT, {
recipes: this.options.recipes.value,
settings: this.options.settings.value,
themes: this.options.themes.value,
pages: this.options.pages.value,
users: this.options.users.value,
groups: this.options.groups.value,
});
},
},
};
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,118 @@
<template>
<v-card :loading="loading">
<v-card-title> {{ $t("settings.backup.create-heading") }} </v-card-title>
<v-card-text class="mt-n3">
<v-text-field
dense
:label="$t('settings.backup.backup-tag')"
v-model="tag"
></v-text-field>
</v-card-text>
<v-card-actions class="mt-n9 flex-wrap">
<v-switch v-model="fullBackup" :label="switchLabel"></v-switch>
<v-spacer></v-spacer>
<v-btn color="success" text @click="createBackup()">
{{ $t("general.create") }}
</v-btn>
</v-card-actions>
<v-expand-transition>
<div v-if="!fullBackup">
<v-card-text class="mt-n4">
<v-row>
<v-col sm="4">
<p>{{ $t("general.options") }}:</p>
<ImportOptions @update-options="updateOptions" class="mt-5" />
</v-col>
<v-col>
<p>{{ $t("general.templates") }}:</p>
<v-checkbox
v-for="template in availableTemplates"
:key="template"
class="mb-n4 mt-n3"
dense
:label="template"
@click="appendTemplate(template)"
></v-checkbox>
</v-col>
</v-row>
</v-card-text>
</div>
</v-expand-transition>
</v-card>
</template>
<script>
import ImportOptions from "./ImportOptions";
import { api } from "@/api";
export default {
components: { ImportOptions },
data() {
return {
tag: null,
fullBackup: true,
loading: false,
options: {
recipes: true,
settings: true,
themes: true,
users: true,
groups: true,
},
availableTemplates: [],
selectedTemplates: [],
};
},
mounted() {
this.getAvailableBackups();
},
computed: {
switchLabel() {
if (this.fullBackup) {
return this.$t("settings.backup.full-backup");
} else return this.$t("settings.backup.partial-backup");
},
},
methods: {
updateOptions(options) {
this.options = options;
},
async getAvailableBackups() {
let response = await api.backups.requestAvailable();
response.templates.forEach(element => {
this.availableTemplates.push(element);
});
},
async createBackup() {
this.loading = true;
let data = {
tag: this.tag,
options: {
recipes: this.options.recipes,
settings: this.options.settings,
themes: this.options.themes,
users: this.options.users,
groups: this.options.groups,
},
templates: this.selectedTemplates,
};
await api.backups.create(data);
this.loading = false;
this.$emit("created");
},
appendTemplate(templateName) {
if (this.selectedTemplates.includes(templateName)) {
let index = this.selectedTemplates.indexOf(templateName);
if (index !== -1) {
this.selectedTemplates.splice(index, 1);
}
} else this.selectedTemplates.push(templateName);
},
},
};
</script>
<style>
</style>

View File

@@ -20,7 +20,7 @@
<v-card-title class="mt-n6">
{{ $t("settings.available-backups") }}
<span>
<UploadBtn
<TheUploadBtn
class="mt-1"
url="/api/backups/upload"
@uploaded="getAvailableBackups"
@@ -33,14 +33,7 @@
@finished="processFinished"
:backups="availableBackups"
/>
<SuccessFailureAlert
ref="report"
:title="$t('settings.backup.backup-restore-report')"
:success-header="$t('settings.backup.successfully-imported')"
:success="successfulImports"
:failed-header="$t('settings.backup.failed-imports')"
:failed="failedImports"
/>
<ImportSummaryDialog ref="report" :import-data="importData" />
</v-card-text>
</v-card>
@@ -48,16 +41,14 @@
<script>
import { api } from "@/api";
import SuccessFailureAlert from "@/components/UI/SuccessFailureAlert";
import ImportSummaryDialog from "@/components/Admin/Backup/ImportSummaryDialog";
import UploadBtn from "@/components/UI/UploadBtn";
import AvailableBackupCard from "@/components/Admin/Backup/AvailableBackupCard";
import NewBackupCard from "@/components/Admin/Backup/NewBackupCard";
import TheUploadBtn from "@/components/UI/Buttons/TheUploadBtn";
import ImportSummaryDialog from "@/components/ImportSummaryDialog";
import AvailableBackupCard from "@/pages/Admin/Backup/AvailableBackupCard";
import NewBackupCard from "@/pages/Admin/Backup/NewBackupCard";
export default {
components: {
SuccessFailureAlert,
UploadBtn,
TheUploadBtn,
AvailableBackupCard,
NewBackupCard,
ImportSummaryDialog,

View File

@@ -0,0 +1,131 @@
<template>
<div>
<ConfirmationDialog
ref="deleteGroupConfirm"
:title="$t('user.confirm-group-deletion')"
:message="
$t('user.are-you-sure-you-want-to-delete-the-group', {
groupName: group.name,
})
"
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>{{
$t("user.group-id-with-value", { groupID: 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"
>
{{ $t("general.delete") }}
</v-btn>
<!-- Coming Soon! -->
<v-btn small color="success" disabled>
{{ $t("general.edit") }}
</v-btn>
</v-card-actions>
</v-card>
</div>
</template>
<script>
const RENDER_EVENT = "update";
import ConfirmationDialog from "@/components/UI/Dialogs/ConfirmationDialog";
import { api } from "@/api";
export default {
components: { ConfirmationDialog },
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: this.$t("user.total-users"),
icon: "mdi-account",
value: this.group.users.length,
},
{
text: this.$t("user.total-mealplans"),
icon: "mdi-food",
value: this.group.mealplans.length,
},
{
text: this.$t("user.webhooks-enabled"),
icon: "mdi-webhook",
value: this.group.webhookEnable
? this.$t("general.yes")
: this.$t("general.no"),
},
{
text: this.$t("user.webhook-time"),
icon: "mdi-clock-outline",
value: this.group.webhookTime,
},
];
},
},
};
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,119 @@
<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="$t('general.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"
>
{{ $t("user.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">
{{ $t("user.create-group") }}
</v-toolbar-title>
<v-spacer></v-spacer>
</v-app-bar>
<v-form ref="newGroup" @submit.prevent="createGroup">
<v-card-text>
<v-text-field
v-model="newGroupName"
:label="$t('user.group-name')"
:rules="[existsRule]"
></v-text-field>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="grey" text @click="groupDialog = false">
{{ $t("general.cancel") }}
</v-btn>
<v-btn color="primary" type="submit">
{{ $t("general.create") }}
</v-btn>
</v-card-actions>
</v-form>
</v-card>
</v-dialog>
</v-card-actions>
<v-card-text>
<v-row>
<v-col
:sm="6"
:md="6"
:lg="4"
: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 "./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

@@ -0,0 +1,242 @@
<template>
<v-card outlined class="mt-n1">
<ConfirmationDialog
ref="deleteUserDialog"
:title="$t('user.confirm-link-deletion')"
:message="
$t('user.are-you-sure-you-want-to-delete-the-link', {
link: activeName,
})
"
icon="mdi-alert"
@confirm="deleteUser"
:width="450"
@close="closeDelete"
/>
<v-toolbar flat>
<v-icon large color="accent" class="mr-1">
mdi-link-variant
</v-icon>
<v-toolbar-title class="headine">
{{ $t("user.sign-up-links") }}
</v-toolbar-title>
<v-spacer> </v-spacer>
<v-dialog v-model="dialog" max-width="500">
<template v-slot:activator="{ on, attrs }">
<v-btn small color="success" dark v-bind="attrs" v-on="on">
{{ $t("user.create-link") }}
</v-btn>
</template>
<v-card>
<v-app-bar dark dense color="primary">
<v-icon left>
mdi-account
</v-icon>
<v-toolbar-title class="headline">
{{ $t("user.create-link") }}
</v-toolbar-title>
<v-spacer></v-spacer>
</v-app-bar>
<v-form ref="newUser" @submit.prevent="save">
<v-card-text>
<v-text-field
v-model="editedItem.name"
:label="$t('user.link-name')"
:rules="[existsRule]"
validate-on-blur
></v-text-field>
<v-checkbox
v-model="editedItem.admin"
:label="$t('user.admin')"
></v-checkbox>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="grey" text @click="close">
{{ $t("general.cancel") }}
</v-btn>
<v-btn color="primary" type="submit">
{{ $t("general.save") }}
</v-btn>
</v-card-actions>
</v-form>
</v-card>
</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 }">
{{ `${baseURL}/sign-up/${item.token}` }}
<v-btn
icon
class="mr-1"
small
color="accent"
@click="updateClipboard(`${baseURL}/sign-up/${item.token}`)"
>
<v-icon>
mdi-content-copy
</v-icon>
</v-btn>
</template>
<template v-slot:item.admin="{ item }">
<v-btn small :color="item.admin ? 'success' : 'error'" text>
<v-icon small left>
mdi-account-cog
</v-icon>
{{ item.admin ? $t("general.yes") : $t("general.no") }}
</v-btn>
</template>
<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>
{{ $t("general.delete") }}
</v-btn>
</template>
</v-data-table>
</v-card-text>
</v-card>
</template>
<script>
import ConfirmationDialog from "@/components/UI/Dialogs/ConfirmationDialog";
import { api } from "@/api";
import { validators } from "@/mixins/validators";
export default {
components: { ConfirmationDialog },
mixins: [validators],
data() {
return {
dialog: false,
activeId: null,
activeName: null,
headers: [
{
text: this.$t("user.link-id"),
align: "start",
sortable: false,
value: "id",
},
{ text: this.$t("general.name"), value: "name" },
{ text: this.$t("general.token"), value: "token" },
{ text: this.$t("user.admin"), value: "admin", align: "center" },
{ text: "", value: "actions", sortable: false, align: "center" },
],
links: [],
editedIndex: -1,
editedItem: {
name: "",
admin: false,
token: "",
id: 0,
},
defaultItem: {
name: "",
token: "",
admin: false,
id: 0,
},
};
},
computed: {
baseURL() {
return window.location.origin;
},
},
watch: {
dialog(val) {
val || this.close();
},
dialogDelete(val) {
val || this.closeDelete();
},
},
created() {
this.initialize();
},
methods: {
updateClipboard(newClip) {
navigator.clipboard.writeText(newClip).then(
function() {
console.log("Copied", newClip);
},
function() {
console.log("Copy Failed", newClip);
}
);
},
async initialize() {
this.links = await api.signUps.getAll();
},
async deleteUser() {
await api.signUps.deleteToken(this.activeId);
this.initialize();
},
editItem(item) {
this.editedIndex = this.links.indexOf(item);
this.editedItem = Object.assign({}, item);
this.dialog = true;
},
deleteItem(item) {
this.activeId = item.token;
this.activeName = item.name;
this.editedIndex = this.links.indexOf(item);
this.editedItem = Object.assign({}, item);
this.$refs.deleteUserDialog.open();
},
deleteItemConfirm() {
this.links.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.links.update(this.editedItem);
this.close();
} else if (this.$refs.newUser.validate()) {
api.signUps.createToken({
name: this.editedItem.name,
admin: this.editedItem.admin,
});
this.close();
}
await this.initialize();
},
},
};
</script>
<style>
</style>

View File

@@ -0,0 +1,283 @@
<template>
<v-card outlined class="mt-n1">
<ConfirmationDialog
ref="deleteUserDialog"
:title="$t('user.confirm-user-deletion')"
:message="
$t('user.are-you-sure-you-want-to-delete-the-user', {
activeName,
activeId,
})
"
icon="mdi-alert"
@confirm="deleteUser"
:width="450"
@close="closeDelete"
/>
<v-toolbar flat>
<v-spacer> </v-spacer>
<div width="100px">
<v-text-field
v-model="search"
class="mr-2"
append-icon="mdi-filter"
:label="$t('general.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">
{{ $t("user.create-user") }}
</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">
{{ $t("user.user-id-with-value", { id: editedItem.id }) }}
</v-toolbar-title>
</v-app-bar>
<v-form ref="newUser" @submit.prevent="save">
<v-card-text>
<v-row>
<v-col cols="12" sm="12" md="6">
<v-text-field
v-model="editedItem.fullName"
:label="$t('user.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="$t('user.email')"
:rules="[existsRule, emailRule]"
validate-on-blur
></v-text-field>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-select
dense
v-model="editedItem.group"
:items="existingGroups"
:label="$t('user.user-group')"
></v-select>
</v-col>
<v-col cols="12" sm="12" md="6" v-if="showPassword">
<v-text-field
dense
v-model="editedItem.password"
:label="$t('user.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="$t('user.admin')"
></v-switch>
</v-col>
</v-row>
</v-card-text>
<v-card-actions>
<v-btn color="info" text @click="resetPassword">
Reset Password
</v-btn>
<v-spacer></v-spacer>
<v-btn color="grey" text @click="close">
{{ $t("general.cancel") }}
</v-btn>
<v-btn color="primary" type="submit">
{{ $t("general.save") }}
</v-btn>
</v-card-actions>
</v-form>
</v-card>
</v-dialog>
</v-toolbar>
<v-divider></v-divider>
<v-card-text>
<v-data-table
:headers="headers"
:items="users"
sort-by="calories"
:search="search"
>
<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>
{{ $t("general.delete") }}
</v-btn>
<v-btn small color="success" @click="editItem(item)">
<v-icon small left class="mr-2">
mdi-pencil
</v-icon>
{{ $t("general.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">
{{ $t("general.reset") }}
</v-btn>
</template>
</v-data-table>
</v-card-text>
</v-card>
</template>
<script>
import ConfirmationDialog from "@/components/UI/Dialogs/ConfirmationDialog";
import { api } from "@/api";
import { validators } from "@/mixins/validators";
export default {
components: { ConfirmationDialog },
mixins: [validators],
data() {
return {
search: "",
dialog: false,
activeId: null,
activeName: null,
headers: [
{
text: this.$t("user.user-id"),
align: "start",
sortable: false,
value: "id",
},
{ text: this.$t("user.full-name"), value: "fullName" },
{ text: this.$t("user.email"), value: "email" },
{ text: this.$t("user.group"), value: "group" },
{ text: this.$t("user.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
? this.$t("user.new-user")
: this.$t("user.edit-user");
},
showPassword() {
return this.editedIndex === -1 ? true : false;
},
existingGroups() {
return this.$store.getters.getGroupNames;
},
},
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) {
await api.users.update(this.editedItem);
this.close();
} else if (this.$refs.newUser.validate()) {
await api.users.create(this.editedItem);
this.close();
}
await this.initialize();
},
resetPassword() {
api.users.resetPassword(this.editedItem.id);
},
},
};
</script>
<style>
</style>

View File

@@ -11,17 +11,17 @@
<v-tabs-slider></v-tabs-slider>
<v-tab>
{{$t('user.users')}}
{{ $t("user.users") }}
<v-icon>mdi-account</v-icon>
</v-tab>
<v-tab>
{{$t('user.sign-up-links')}}
{{ $t("user.sign-up-links") }}
<v-icon>mdi-account-plus-outline</v-icon>
</v-tab>
<v-tab>
{{$t('user.groups')}}
{{ $t("user.groups") }}
<v-icon>mdi-account-group</v-icon>
</v-tab>
</v-tabs>
@@ -42,9 +42,9 @@
</template>
<script>
import TheUserTable from "@/components/Admin/ManageUsers/TheUserTable";
import GroupDashboard from "@/components/Admin/ManageUsers/GroupDashboard";
import TheSignUpTable from "@/components/Admin/ManageUsers/TheSignUpTable";
import TheUserTable from "./TheUserTable";
import GroupDashboard from "./GroupDashboard";
import TheSignUpTable from "./TheSignUpTable";
export default {
components: { TheUserTable, GroupDashboard, TheSignUpTable },
data() {

View File

@@ -82,7 +82,7 @@
<script>
import { api } from "@/api";
import TimePickerDialog from "@/components/Admin/MealPlanner/TimePickerDialog";
import TimePickerDialog from "@/components/FormHelpers/TimePickerDialog";
import CategoryTagSelector from "@/components/FormHelpers/CategoryTagSelector";
export default {
components: {

View File

@@ -0,0 +1,110 @@
<template>
<v-card outlined class="my-2" :loading="loading">
<MigrationDialog ref="migrationDialog" />
<v-card-title>
{{ title }}
<v-spacer></v-spacer>
<span>
<TheUploadBtn
class="mt-1"
:url="`/api/migrations/${folder}/upload`"
fileName="archive"
@uploaded="$emit('refresh')"
:post="true"
/>
</span>
</v-card-title>
<v-card-text> {{ description }}</v-card-text>
<div v-if="available[0]">
<v-card
outlined
v-for="migration in available"
:key="migration.name"
class="ma-2"
>
<v-card-text>
<v-row align="center">
<v-col cols="2">
<v-icon large color="primary">mdi-import</v-icon>
</v-col>
<v-col cols="10">
<div class="text-truncate">
<strong>{{ migration.name }}</strong>
</div>
<div class="text-truncate">
{{ readableTime(migration.date) }}
</div>
</v-col>
</v-row>
</v-card-text>
<v-card-actions class="mt-n6">
<v-spacer></v-spacer>
<v-btn color="error" text @click="deleteMigration(migration.name)">
{{ $t("general.delete") }}
</v-btn>
<v-btn
color="accent"
text
@click="importMigration(migration.name)"
:loading="loading"
:disabled="loading"
>
{{ $t("general.import") }}
</v-btn>
</v-card-actions>
</v-card>
</div>
<div v-else>
<v-card outlined class="text-center ma-2">
<v-card-text>
{{ $t("migration.no-migration-data-available") }}
</v-card-text>
</v-card>
</div>
<br />
</v-card>
</template>
<script>
import TheUploadBtn from "@/components/UI/Buttons/TheUploadBtn";
import utils from "@/utils";
import { api } from "@/api";
import MigrationDialog from "./MigrationDialog";
export default {
props: {
folder: String,
title: String,
description: String,
available: Array,
},
components: {
TheUploadBtn,
MigrationDialog,
},
data() {
return {
loading: false,
};
},
methods: {
deleteMigration(file_name) {
api.migrations.delete(this.folder, file_name);
this.$emit("refresh");
},
async importMigration(file_name) {
this.loading = true;
let response = await api.migrations.import(this.folder, file_name);
this.$refs.migrationDialog.open(response);
// this.$emit("imported", response.successful, response.failed);
this.loading = false;
},
readableTime(timestamp) {
let date = new Date(timestamp);
return utils.getDateAsText(date);
},
},
};
</script>
<style>
</style>

View File

@@ -0,0 +1,109 @@
<template>
<div class="text-center">
<v-dialog v-model="dialog" width="70%">
<v-card>
<v-app-bar dark color="primary mb-2">
<v-icon large left>
mdi-import
</v-icon>
<v-toolbar-title class="headline">
Migration Summary
</v-toolbar-title>
<v-spacer></v-spacer>
</v-app-bar>
<v-card-text class="mb-n4">
<v-row>
<div v-for="values in allNumbers" :key="values.title">
<v-card-text>
<div>
<h3>{{ values.title }}</h3>
</div>
<div class="success--text">Success: {{ values.success }}</div>
<div class="error--text">Failed: {{ values.failure }}</div>
</v-card-text>
</div>
</v-row>
</v-card-text>
<v-tabs v-model="tab">
<v-tab>{{ $t("general.recipes") }}</v-tab>
</v-tabs>
<v-tabs-items v-model="tab">
<v-tab-item v-for="(table, index) in allTables" :key="index">
<v-card flat>
<DataTable :data-headers="importHeaders" :data-set="table" />
</v-card>
</v-tab-item>
</v-tabs-items>
</v-card>
</v-dialog>
</div>
</template>
<script>
import DataTable from "@/components/ImportSummaryDialog";
export default {
components: {
DataTable,
},
data: () => ({
tab: null,
dialog: false,
recipeData: [],
themeData: [],
settingsData: [],
userData: [],
groupData: [],
pageData: [],
importHeaders: [
{
text: "Status",
value: "status",
},
{
text: "Name",
align: "start",
sortable: true,
value: "name",
},
{ text: "Exception", value: "data-table-expand", align: "center" },
],
allDataTables: [],
}),
computed: {
recipeNumbers() {
return this.calculateNumbers(this.$t("general.recipes"), this.recipeData);
},
allNumbers() {
return [this.recipeNumbers];
},
allTables() {
return [this.recipeData];
},
},
methods: {
calculateNumbers(title, list_array) {
if (!list_array) return;
let numbers = { title: title, success: 0, failure: 0 };
list_array.forEach(element => {
if (element.status) {
numbers.success++;
} else numbers.failure++;
});
return numbers;
},
open(importData) {
this.recipeData = importData;
this.dialog = true;
},
},
};
</script>
<style>
</style>

View File

@@ -1,13 +1,5 @@
<template>
<div>
<SuccessFailureAlert
:title="$t('migration.migration-report')"
ref="report"
:failedHeader="$t('migration.failed-imports')"
:failed="failed"
:successHeader="$t('migration.successful-imports')"
:success="success"
/>
<v-card :loading="loading">
<v-card-title class="headline">
{{ $t("migration.recipe-migration") }}
@@ -42,13 +34,11 @@
<script>
import MigrationCard from "@/components/Admin/Migration/MigrationCard";
import SuccessFailureAlert from "@/components/UI/SuccessFailureAlert";
import MigrationCard from "./MigrationCard";
import { api } from "@/api";
export default {
components: {
MigrationCard,
SuccessFailureAlert,
},
data() {
return {

View File

@@ -68,7 +68,7 @@
</v-card-text>
<v-card-actions>
<UploadBtn
<TheUploadBtn
icon="mdi-image-area"
:text="$t('user.upload-photo')"
:url="userProfileImage"
@@ -145,13 +145,13 @@
<script>
// import AvatarPicker from '@/components/AvatarPicker'
import UploadBtn from "@/components/UI/UploadBtn";
import TheUploadBtn from "@/components/UI/Buttons/TheUploadBtn";
import { api } from "@/api";
import { validators } from "@/mixins/validators";
import { initials } from "@/mixins/initials";
export default {
components: {
UploadBtn,
TheUploadBtn,
},
mixins: [validators, initials],
data() {

View File

@@ -0,0 +1,99 @@
<template>
<v-dialog v-model="pageDialog" max-width="500">
<v-card>
<v-app-bar dark dense color="primary">
<v-icon left>
mdi-page-layout-body
</v-icon>
<v-toolbar-title class="headline">
{{ title }}
</v-toolbar-title>
<v-spacer></v-spacer>
</v-app-bar>
<v-form ref="newGroup" @submit.prevent="submitForm">
<v-card-text>
<v-text-field
autofocus
v-model="page.name"
:label="$t('settings.page-name')"
></v-text-field>
<CategoryTagSelector
v-model="page.categories"
ref="categoryFormSelector"
@mounted="catMounted = true"
:tag-selector="false"
/>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="grey" text @click="pageDialog = false">
{{ $t("general.cancel") }}
</v-btn>
<v-btn color="primary" type="submit">
{{ buttonText }}
</v-btn>
</v-card-actions>
</v-form>
</v-card>
</v-dialog>
</template>
<script>
const NEW_PAGE_EVENT = "refresh-page";
import { api } from "@/api";
import CategoryTagSelector from "@/components/FormHelpers/CategoryTagSelector";
export default {
components: {
CategoryTagSelector,
},
data() {
return {
catMounted: false,
title: "",
buttonText: "",
create: false,
pageDialog: false,
page: {
name: "",
position: 0,
categories: [],
},
};
},
watch: {
catMounted(val) {
if (val) this.pushSelected();
},
},
methods: {
open(parameters) {
this.page = parameters.data;
this.create = parameters.create;
this.buttonText = parameters.buttonText;
this.title = parameters.title;
this.pageDialog = true;
if (this.catMounted) this.pushSelected();
},
pushSelected() {
this.$refs.categoryFormSelector.setInit(this.page.categories);
},
async submitForm() {
if (this.create) {
await api.siteSettings.createPage(this.page);
} else {
await api.siteSettings.updatePage(this.page);
}
this.pageDialog = false;
this.page.categories = [];
this.$emit(NEW_PAGE_EVENT);
},
},
};
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,129 @@
<template>
<v-card flat>
<CreatePageDialog ref="createDialog" @refresh-page="getPages" />
<v-card-text>
<h2 class="mt-1 mb-1 ">
{{$t('settings.custom-pages')}}
<span>
<v-btn color="success" @click="newPage" small class="ml-3">
{{$t('general.create')}}
</v-btn>
</span>
</h2>
<draggable class="row mt-1" v-model="customPages">
<v-col
:sm="6"
:md="6"
:lg="4"
:xl="3"
v-for="(item, index) in customPages"
:key="item + item.id"
>
<v-card>
<v-card-text class="mb-0 pb-0">
<h3>{{ item.name }}</h3>
<v-divider></v-divider>
</v-card-text>
<v-card-text class="mt-0">
<div>
<v-chip
v-for="cat in item.categories"
:key="cat.slug + cat.id"
class="my-2 mr-2"
label
small
color="accent lighten-1"
>
{{ cat.name }}
</v-chip>
</div>
</v-card-text>
<v-card-actions>
<v-btn text small color="error" @click="deletePage(item.id)">
{{$t('general.delete')}}
</v-btn>
<v-spacer> </v-spacer>
<v-btn small text color="success" @click="editPage(index)">
{{$t('general.edit')}}
</v-btn>
</v-card-actions>
</v-card>
</v-col>
</draggable>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="success" @click="savePages">
{{$t('general.save')}}
</v-btn>
</v-card-actions>
</v-card>
</template>
<script>
import draggable from "vuedraggable";
import CreatePageDialog from "./CreatePageDialog";
import { api } from "@/api";
export default {
components: {
draggable,
CreatePageDialog,
},
data() {
return {
pageDialog: false,
customPages: [],
newPageData: {
create: true,
title: this.$t('settings.new-page'),
buttonText: this.$t('general.create'),
data: {
name: "",
categories: [],
position: 0,
},
},
editPageData: {
create: false,
title: this.$t('settings.edit-page'),
buttonText: this.$t('general.update'),
data: {},
},
};
},
async mounted() {
this.getPages();
},
methods: {
async getPages() {
this.customPages = await api.siteSettings.getPages();
this.customPages.sort((a, b) => a.position - b.position);
},
async deletePage(id) {
await api.siteSettings.deletePage(id);
this.getPages();
},
async savePages() {
this.customPages.forEach((element, index) => {
element.position = index;
});
await api.siteSettings.updateAllPages(this.customPages);
this.getPages();
},
editPage(index) {
this.editPageData.data = this.customPages[index];
this.$refs.createDialog.open(this.editPageData);
},
newPage() {
this.newPageData.position = this.customPages.length;
this.$refs.createDialog.open(this.newPageData);
},
},
};
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,235 @@
<template>
<v-card flat>
<v-card-text>
<h2 class="mt-1 mb-1">{{ $t("settings.homepage.home-page") }}</h2>
<v-row align="center" justify="center" dense class="mb-n7 pb-n5">
<v-col cols="12" sm="3" md="2">
<v-switch
v-model="settings.showRecent"
:label="$t('settings.homepage.show-recent')"
></v-switch>
</v-col>
<v-col cols="12" sm="5" md="5">
<v-slider
class="pt-sm-4"
:label="$t('settings.homepage.card-per-section')"
v-model="settings.cardsPerSection"
max="30"
dense
color="primary"
min="3"
thumb-label
>
</v-slider>
</v-col>
<v-spacer></v-spacer>
</v-row>
</v-card-text>
<v-card-text>
<v-row>
<v-col cols="12" sm="6">
<v-card outlined min-height="350px">
<v-app-bar dark dense color="primary">
<v-icon left>
mdi-home
</v-icon>
<v-toolbar-title class="headline">
{{ $t("settings.homepage.home-page-sections") }}
</v-toolbar-title>
<v-spacer></v-spacer>
</v-app-bar>
<v-list height="300" dense style="overflow:auto">
<v-list-item-group>
<draggable
v-model="settings.categories"
group="categories"
:style="{
minHeight: `150px`,
}"
>
<v-list-item
v-for="(item, index) in settings.categories"
:key="`${item.name}-${index}`"
>
<v-list-item-icon>
<v-icon>mdi-menu</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title v-text="item.name"></v-list-item-title>
</v-list-item-content>
<v-list-item-icon @click="deleteActiveCategory(index)">
<v-icon>mdi-delete</v-icon>
</v-list-item-icon>
</v-list-item>
</draggable>
</v-list-item-group>
</v-list>
</v-card>
</v-col>
<v-col cols="12" sm="6">
<v-card outlined height="350px">
<v-app-bar dark dense color="primary">
<v-icon left>
mdi-tag
</v-icon>
<v-toolbar-title class="headline">
{{ $t("settings.homepage.all-categories") }}
</v-toolbar-title>
<v-spacer></v-spacer>
<NewCategoryTagDialog :tag-dialog="false" />
</v-app-bar>
<v-list height="300" dense style="overflow:auto">
<v-list-item-group>
<draggable
v-model="allCategories"
group="categories"
:style="{
minHeight: `150px`,
}"
>
<v-list-item
v-for="(item, index) in allCategories"
:key="`${item.name}-${index}`"
>
<v-list-item-icon>
<v-icon>mdi-menu</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title v-text="item.name"></v-list-item-title>
</v-list-item-content>
<v-list-item-icon
@click="deleteCategoryfromDatabase(item.slug)"
>
<v-icon>mdi-delete</v-icon>
</v-list-item-icon>
</v-list-item>
</draggable>
</v-list-item-group>
</v-list>
</v-card>
</v-col>
</v-row>
</v-card-text>
<v-card-text>
<h2 class="mt-1 mb-4">{{ $t("settings.locale-settings") }}</h2>
<v-row>
<v-col cols="12" md="3" sm="12">
<LanguageSelector @select-lang="writeLang" :site-settings="true" />
</v-col>
<v-col cols="12" md="3" sm="12">
<v-select
dense
prepend-icon="mdi-calendar-week-begin"
v-model="settings.firstDayOfWeek"
:items="allDays"
item-text="name"
item-value="value"
:label="$t('settings.first-day-of-week')"
/>
</v-col>
</v-row>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="success" @click="saveSettings" class="mr-2">
<v-icon left> mdi-content-save </v-icon>
{{ $t("general.save") }}
</v-btn>
</v-card-actions>
</v-card>
</template>
<script>
import { api } from "@/api";
import LanguageSelector from "@/components/FormHelpers/LanguageSelector";
import draggable from "vuedraggable";
import NewCategoryTagDialog from "@/components/UI/Dialogs/NewCategoryTagDialog.vue";
export default {
components: {
draggable,
LanguageSelector,
NewCategoryTagDialog,
},
data() {
return {
settings: {
language: "en",
firstDayOfWeek: 0,
showRecent: null,
cardsPerSection: null,
categories: [],
},
};
},
mounted() {
this.getOptions();
},
computed: {
allCategories() {
return this.$store.getters.getAllCategories;
},
allDays() {
return [
{
name: this.$t("general.sunday"),
value: 0,
},
{
name: this.$t("general.monday"),
value: 1,
},
{
name: this.$t("general.tuesday"),
value: 2,
},
{
name: this.$t("general.wednesday"),
value: 3,
},
{
name: this.$t("general.thursday"),
value: 4,
},
{
name: this.$t("general.friday"),
value: 5,
},
{
name: this.$t("general.saturday"),
value: 6,
},
];
},
},
methods: {
writeLang(val) {
this.settings.language = val;
},
deleteCategoryfromDatabase(category) {
api.categories.delete(category);
},
async getOptions() {
this.settings = await api.siteSettings.get();
},
deleteActiveCategory(index) {
this.settings.categories.splice(index, 1);
},
async saveSettings() {
const newSettings = await api.siteSettings.update(this.settings);
console.log("New Settings", newSettings);
this.getOptions();
},
},
};
</script>
<style>
</style>

View File

@@ -20,8 +20,8 @@
</template>
<script>
import HomePageSettings from "@/components/Admin/General/HomePageSettings";
import CustomPageCreator from "@/components/Admin/General/CustomPageCreator";
import HomePageSettings from "./HomePageSettings";
import CustomPageCreator from "./CustomPageCreator";
export default {
components: {

View File

@@ -0,0 +1,91 @@
<template>
<div>
<v-btn text color="info" @click="dialog = true">
{{ $t("settings.add-a-new-theme") }}
</v-btn>
<v-dialog v-model="dialog" width="500">
<v-card>
<v-app-bar dense dark color="primary mb-2">
<v-icon large left class="mt-1">
mdi-format-color-fill
</v-icon>
<v-toolbar-title class="headline">
{{ $t("settings.add-a-new-theme") }}
</v-toolbar-title>
<v-spacer></v-spacer>
</v-app-bar>
<v-card-title> </v-card-title>
<v-form @submit.prevent="select">
<v-card-text>
<v-text-field
:label="$t('settings.theme.theme-name')"
v-model="themeName"
:rules="[rules.required]"
></v-text-field>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="grey" text @click="dialog = false">
{{ $t("general.cancel") }}
</v-btn>
<v-btn color="success" text type="submit" :disabled="!themeName">
{{ $t("general.create") }}
</v-btn>
</v-card-actions>
</v-form>
</v-card>
</v-dialog>
</div>
</template>
<script>
export default {
props: {
buttonText: String,
value: String,
},
data() {
return {
dialog: false,
themeName: "",
rules: {
required: val =>
!!val || this.$t("settings.theme.theme-name-is-required"),
},
};
},
watch: {
color() {
this.updateColor();
},
},
methods: {
randomColor() {
return "#" + Math.floor(Math.random() * 16777215).toString(16);
},
select() {
const newTheme = {
name: this.themeName,
colors: {
primary: "#E58325",
accent: "#00457A",
secondary: "#973542",
success: "#5AB1BB",
info: "#4990BA",
warning: "#FF4081",
error: "#EF5350",
},
};
this.$emit("new-theme", newTheme);
this.dialog = false;
},
},
};
</script>
<style>
</style>

View File

@@ -0,0 +1,91 @@
<template>
<div>
<ConfirmationDialog
:title="$t('settings.theme.delete-theme')"
:message="$t('settings.theme.are-you-sure-you-want-to-delete-this-theme')"
color="error"
icon="mdi-alert-circle"
ref="deleteThemeConfirm"
v-on:confirm="deleteSelectedTheme()"
/>
<v-card flat outlined class="ma-2">
<v-card-text class="mb-n5 mt-n2">
<h3>
{{ theme.name }}
{{ current ? $t("general.current-parenthesis") : "" }}
</h3>
</v-card-text>
<v-card-text>
<v-row flex align-center>
<v-card
v-for="(color, index) in theme.colors"
:key="index"
class="ma-1 mx-auto"
height="34"
width="36"
:color="color"
>
</v-card>
</v-row>
</v-card-text>
<v-divider></v-divider>
<v-card-actions>
<v-btn text color="error" @click="confirmDelete">
{{ $t("general.delete") }}
</v-btn>
<v-spacer></v-spacer>
<!-- <v-btn text color="accent" @click="editTheme">Edit</v-btn> -->
<v-btn text color="success" @click="saveThemes">{{
$t("general.apply")
}}</v-btn>
</v-card-actions>
</v-card>
</div>
</template>
<script>
import ConfirmationDialog from "@/components/UI/Dialogs/ConfirmationDialog";
import { api } from "@/api";
const DELETE_EVENT = "delete";
const APPLY_EVENT = "apply";
const EDIT_EVENT = "edit";
export default {
components: {
ConfirmationDialog,
},
props: {
theme: Object,
current: {
default: false,
},
},
methods: {
confirmDelete() {
if (this.theme.name === "default") {
// Notify User Can't Delete Default
} else if (this.theme !== {}) {
this.$refs.deleteThemeConfirm.open();
}
},
async deleteSelectedTheme() {
//Delete Theme from DB
await api.themes.delete(this.theme.name);
//Get the new list of available from DB
this.availableThemes = await api.themes.requestAll();
this.$emit(DELETE_EVENT);
},
async saveThemes() {
this.$store.commit("setTheme", this.theme);
this.$emit(APPLY_EVENT, this.theme);
},
editTheme() {
this.$emit(EDIT_EVENT);
},
},
};
</script>
<style>
</style>

View File

@@ -135,9 +135,9 @@
<script>
import { api } from "@/api";
import ColorPickerDialog from "@/components/Admin/Theme/ColorPickerDialog";
import NewThemeDialog from "@/components/Admin/Theme/NewThemeDialog";
import ThemeCard from "@/components/Admin/Theme/ThemeCard";
import ColorPickerDialog from "@/components/FormHelpers/ColorPickerDialog";
import NewThemeDialog from "./NewThemeDialog";
import ThemeCard from "./ThemeCard";
export default {
components: {