chore: migrate remaining pages to script setup (#7310)

This commit is contained in:
Kuchenpirat
2026-03-24 16:07:08 +01:00
committed by GitHub
parent 27cb585c80
commit 18b3c4beab
57 changed files with 4160 additions and 4971 deletions

View File

@@ -32,129 +32,39 @@
</v-container>
</template>
<script lang="ts">
<script setup lang="ts">
import HouseholdPreferencesEditor from "~/components/Domain/Household/HouseholdPreferencesEditor.vue";
import { useHouseholdSelf } from "~/composables/use-households";
import type { ReadHouseholdPreferences } from "~/lib/api/types/household";
import { alert } from "~/composables/use-toast";
import type { VForm } from "~/types/auto-forms";
export default defineNuxtComponent({
components: {
HouseholdPreferencesEditor,
},
definePageMeta({
middleware: ["can-manage-household-only"],
setup() {
const { household, actions: householdActions } = useHouseholdSelf();
const i18n = useI18n();
useSeoMeta({
title: i18n.t("household.household"),
});
const refHouseholdEditForm = ref<VForm | null>(null);
type Preference = {
key: keyof ReadHouseholdPreferences;
value: boolean;
label: string;
description: string;
};
const preferencesEditor = computed<Preference[]>(() => {
if (!household.value || !household.value.preferences) {
return [];
}
return [
{
key: "recipePublic",
value: household.value.preferences.recipePublic || false,
label: i18n.t("household.allow-users-outside-of-your-household-to-see-your-recipes"),
description: i18n.t("household.allow-users-outside-of-your-household-to-see-your-recipes-description"),
} as Preference,
{
key: "recipeShowNutrition",
value: household.value.preferences.recipeShowNutrition || false,
label: i18n.t("group.show-nutrition-information"),
description: i18n.t("group.show-nutrition-information-description"),
} as Preference,
{
key: "recipeShowAssets",
value: household.value.preferences.recipeShowAssets || false,
label: i18n.t("group.show-recipe-assets"),
description: i18n.t("group.show-recipe-assets-description"),
} as Preference,
{
key: "recipeLandscapeView",
value: household.value.preferences.recipeLandscapeView || false,
label: i18n.t("group.default-to-landscape-view"),
description: i18n.t("group.default-to-landscape-view-description"),
} as Preference,
{
key: "recipeDisableComments",
value: household.value.preferences.recipeDisableComments || false,
label: i18n.t("group.disable-users-from-commenting-on-recipes"),
description: i18n.t("group.disable-users-from-commenting-on-recipes-description"),
} as Preference,
];
});
const allDays = [
{
name: i18n.t("general.sunday"),
value: 0,
},
{
name: i18n.t("general.monday"),
value: 1,
},
{
name: i18n.t("general.tuesday"),
value: 2,
},
{
name: i18n.t("general.wednesday"),
value: 3,
},
{
name: i18n.t("general.thursday"),
value: 4,
},
{
name: i18n.t("general.friday"),
value: 5,
},
{
name: i18n.t("general.saturday"),
value: 6,
},
];
async function handleSubmit() {
if (!refHouseholdEditForm.value?.validate() || !household.value?.preferences) {
console.log(refHouseholdEditForm.value?.validate());
return;
}
const data = await householdActions.updatePreferences();
if (data) {
alert.success(i18n.t("settings.settings-updated"));
}
else {
alert.error(i18n.t("settings.settings-update-failed"));
}
}
return {
household,
householdActions,
allDays,
preferencesEditor,
refHouseholdEditForm,
handleSubmit,
};
},
});
const { household, actions: householdActions } = useHouseholdSelf();
const i18n = useI18n();
useSeoMeta({
title: i18n.t("household.household"),
});
const refHouseholdEditForm = ref<VForm | null>(null);
async function handleSubmit() {
if (!refHouseholdEditForm.value?.validate() || !household.value?.preferences) {
console.log(refHouseholdEditForm.value?.validate());
return;
}
const data = await householdActions.updatePreferences();
if (data) {
alert.success(i18n.t("settings.settings-updated"));
}
else {
alert.error(i18n.t("settings.settings-update-failed"));
}
}
</script>
<style lang="css">

View File

@@ -86,7 +86,7 @@
</v-container>
</template>
<script lang="ts">
<script setup lang="ts">
import { isSameDay, addDays, parseISO, format, isValid } from "date-fns";
import RecipeDialogAddToShoppingList from "~/components/Domain/Recipe/RecipeDialogAddToShoppingList.vue";
import { useHouseholdSelf } from "~/composables/use-households";
@@ -95,185 +95,163 @@ import { useUserMealPlanPreferences } from "~/composables/use-users/preferences"
import type { ShoppingListSummary } from "~/lib/api/types/household";
import { useUserApi } from "~/composables/api";
export default defineNuxtComponent({
components: {
RecipeDialogAddToShoppingList,
},
setup() {
const TABS = {
view: "household-mealplan-planner-view",
edit: "household-mealplan-planner-edit",
};
const TABS = {
view: "household-mealplan-planner-view",
edit: "household-mealplan-planner-edit",
};
const route = useRoute();
const router = useRouter();
const i18n = useI18n();
const api = useUserApi();
const { household } = useHouseholdSelf();
const route = useRoute();
const router = useRouter();
const i18n = useI18n();
const api = useUserApi();
const { household } = useHouseholdSelf();
useSeoMeta({
title: i18n.t("meal-plan.dinner-this-week"),
});
const mealPlanPreferences = useUserMealPlanPreferences();
const numberOfDays = ref<number>(mealPlanPreferences.value.numberOfDays || 7);
watch(numberOfDays, (val) => {
mealPlanPreferences.value.numberOfDays = Number(val);
});
// Force to /view if current route is /planner
if (route.path === "/household/mealplan/planner") {
router.push({
name: TABS.view,
query: route.query,
});
}
function safeParseISO(date: string, fallback: Date | undefined = undefined) {
try {
const parsed = parseISO(date);
return isValid(parsed) ? parsed : fallback;
}
catch {
return fallback;
}
}
// Initialize dates from query parameters or defaults
const initialStartDate = safeParseISO(route.query.start as string, new Date());
const initialEndDate = safeParseISO(route.query.end as string, addDays(new Date(), adjustForToday(numberOfDays.value)));
const state = ref({
range: [initialStartDate, initialEndDate] as [Date, Date],
start: initialStartDate,
picker: false,
end: initialEndDate,
shoppingListDialog: false,
addAllLoading: false,
});
const shoppingLists = ref<ShoppingListSummary[]>();
const firstDayOfWeek = computed(() => {
return household.value?.preferences?.firstDayOfWeek || 0;
});
const weekRange = computed(() => {
const sorted = [...state.value.range].sort((a, b) => a.getTime() - b.getTime());
const start = sorted[0];
const end = sorted[sorted.length - 1];
if (start && end) {
return { start, end };
}
return {
start: new Date(),
end: addDays(new Date(), adjustForToday(numberOfDays.value)),
};
});
// Update query parameters when date range changes
watch(weekRange, (newRange) => {
// Keep current route name and params, just update the query
router.replace({
name: route.name || TABS.view,
params: route.params,
query: {
...route.query,
start: format(newRange.start, "yyyy-MM-dd"),
end: format(newRange.end, "yyyy-MM-dd"),
},
});
}, { immediate: true });
const { mealplans, actions } = useMealplans(weekRange);
function filterMealByDate(date: Date) {
if (!mealplans.value) return [];
return mealplans.value.filter((meal) => {
const mealDate = parseISO(meal.date);
return isSameDay(mealDate, date);
});
}
function adjustForToday(days: number) {
// The use case for this function is "how many days are we adding to 'today'?"
// e.g. If the user wants 7 days, we substract one to do "today + 6"
return days > 0 ? days - 1 : days + 1;
}
const days = computed(() => {
const numDays
= Math.floor((weekRange.value.end.getTime() - weekRange.value.start.getTime()) / (1000 * 60 * 60 * 24)) + 1;
// Calculate absolute value
if (numDays < 0) return [];
return Array.from(Array(numDays).keys()).map(
(i) => {
const date = new Date(weekRange.value.start.getTime());
date.setDate(date.getDate() + i);
return date;
},
);
});
const mealsByDate = computed(() => {
return days.value.map((day) => {
return { date: day, meals: filterMealByDate(day) };
});
});
const hasRecipes = computed(() => {
return mealsByDate.value.some(day => day.meals.some(meal => meal.recipe));
});
const weekRecipesWithScales = computed(() => {
const allRecipes: any[] = [];
for (const day of mealsByDate.value) {
for (const meal of day.meals) {
if (meal.recipe) {
allRecipes.push(meal.recipe);
}
}
}
return allRecipes.map(recipe => ({
scale: 1,
...recipe,
}));
});
async function getShoppingLists() {
const { data } = await api.shopping.lists.getAll(1, -1, { orderBy: "name", orderDirection: "asc" });
if (data) {
shoppingLists.value = data.items as ShoppingListSummary[] ?? [];
}
}
async function addAllToList() {
state.value.addAllLoading = true;
await getShoppingLists();
state.value.shoppingListDialog = true;
state.value.addAllLoading = false;
}
return {
TABS,
route,
state,
actions,
mealsByDate,
weekRange,
firstDayOfWeek,
numberOfDays,
hasRecipes,
shoppingLists,
weekRecipesWithScales,
addAllToList,
};
},
useSeoMeta({
title: i18n.t("meal-plan.dinner-this-week"),
});
const mealPlanPreferences = useUserMealPlanPreferences();
const numberOfDays = ref<number>(mealPlanPreferences.value.numberOfDays || 7);
watch(numberOfDays, (val) => {
mealPlanPreferences.value.numberOfDays = Number(val);
});
// Force to /view if current route is /planner
if (route.path === "/household/mealplan/planner") {
router.push({
name: TABS.view,
query: route.query,
});
}
function safeParseISO(date: string, fallback: Date | undefined = undefined) {
try {
const parsed = parseISO(date);
return isValid(parsed) ? parsed : fallback;
}
catch {
return fallback;
}
}
// Initialize dates from query parameters or defaults
const initialStartDate = safeParseISO(route.query.start as string, new Date());
const initialEndDate = safeParseISO(route.query.end as string, addDays(new Date(), adjustForToday(numberOfDays.value)));
const state = ref({
range: [initialStartDate, initialEndDate] as [Date, Date],
start: initialStartDate,
picker: false,
end: initialEndDate,
shoppingListDialog: false,
addAllLoading: false,
});
const shoppingLists = ref<ShoppingListSummary[]>();
const firstDayOfWeek = computed(() => {
return household.value?.preferences?.firstDayOfWeek || 0;
});
const weekRange = computed(() => {
const sorted = [...state.value.range].sort((a, b) => a.getTime() - b.getTime());
const start = sorted[0];
const end = sorted[sorted.length - 1];
if (start && end) {
return { start, end };
}
return {
start: new Date(),
end: addDays(new Date(), adjustForToday(numberOfDays.value)),
};
});
// Update query parameters when date range changes
watch(weekRange, (newRange) => {
// Keep current route name and params, just update the query
router.replace({
name: route.name || TABS.view,
params: route.params,
query: {
...route.query,
start: format(newRange.start, "yyyy-MM-dd"),
end: format(newRange.end, "yyyy-MM-dd"),
},
});
}, { immediate: true });
const { mealplans, actions } = useMealplans(weekRange);
function filterMealByDate(date: Date) {
if (!mealplans.value) return [];
return mealplans.value.filter((meal) => {
const mealDate = parseISO(meal.date);
return isSameDay(mealDate, date);
});
}
function adjustForToday(days: number) {
// The use case for this function is "how many days are we adding to 'today'?"
// e.g. If the user wants 7 days, we substract one to do "today + 6"
return days > 0 ? days - 1 : days + 1;
}
const days = computed(() => {
const numDays
= Math.floor((weekRange.value.end.getTime() - weekRange.value.start.getTime()) / (1000 * 60 * 60 * 24)) + 1;
// Calculate absolute value
if (numDays < 0) return [];
return Array.from(Array(numDays).keys()).map(
(i) => {
const date = new Date(weekRange.value.start.getTime());
date.setDate(date.getDate() + i);
return date;
},
);
});
const mealsByDate = computed(() => {
return days.value.map((day) => {
return { date: day, meals: filterMealByDate(day) };
});
});
const hasRecipes = computed(() => {
return mealsByDate.value.some(day => day.meals.some(meal => meal.recipe));
});
const weekRecipesWithScales = computed(() => {
const allRecipes: any[] = [];
for (const day of mealsByDate.value) {
for (const meal of day.meals) {
if (meal.recipe) {
allRecipes.push(meal.recipe);
}
}
}
return allRecipes.map(recipe => ({
scale: 1,
...recipe,
}));
});
async function getShoppingLists() {
const { data } = await api.shopping.lists.getAll(1, -1, { orderBy: "name", orderDirection: "asc" });
if (data) {
shoppingLists.value = data.items as ShoppingListSummary[] ?? [];
}
}
async function addAllToList() {
state.value.addAllLoading = true;
await getShoppingLists();
state.value.shoppingListDialog = true;
state.value.addAllLoading = false;
}
</script>
<style lang="css">

View File

@@ -129,9 +129,9 @@
</v-icon>
</v-btn>
<v-menu offset-y>
<template #activator="{ props }">
<template #activator="{ props: menuProps }">
<v-chip
v-bind="props"
v-bind="menuProps"
label
variant="elevated"
size="small"
@@ -232,7 +232,7 @@
</div>
</template>
<script lang="ts">
<script setup lang="ts">
import { format } from "date-fns";
import type { SortableEvent } from "sortablejs";
import { VueDraggable } from "vue-draggable-plus";
@@ -246,194 +246,157 @@ import { useHouseholdSelf } from "~/composables/use-households";
import { normalizeFilter } from "~/composables/use-utils";
import { useRecipeSearch } from "~/composables/recipes/use-recipe-search";
export default defineNuxtComponent({
components: {
VueDraggable,
RecipeCardImage,
const props = defineProps<{
mealplans: MealsByDate[];
actions: ReturnType<typeof useMealplans>["actions"];
}>();
const api = useUserApi();
const auth = useMealieAuth();
const { household } = useHouseholdSelf();
const requiredRule = (value: any) => !!value || "Required.";
const state = ref({
dialog: false,
});
const firstDayOfWeek = computed(() => {
return household.value?.preferences?.firstDayOfWeek || 0;
});
// Local mutable meals object
const mealplansByDate = reactive<{ [date: string]: UpdatePlanEntry[] }>({});
watch(
() => props.mealplans,
(plans) => {
for (const plan of plans) {
mealplansByDate[plan.date.toString()] = plan.meals ? [...plan.meals] : [];
}
// Remove any dates that no longer exist
Object.keys(mealplansByDate).forEach((date) => {
if (!plans.find(p => p.date.toString() === date)) {
mealplansByDate[date] = [];
}
});
},
props: {
mealplans: {
type: Array as () => MealsByDate[],
required: true,
},
actions: {
type: Object as () => ReturnType<typeof useMealplans>["actions"],
required: true,
},
},
setup(props) {
const api = useUserApi();
const auth = useMealieAuth();
const { household } = useHouseholdSelf();
const requiredRule = (value: any) => !!value || "Required.";
{ immediate: true, deep: true },
);
const state = ref({
dialog: false,
});
function onMoveCallback(evt: SortableEvent) {
const supportedEvents = ["drop", "touchend"];
const firstDayOfWeek = computed(() => {
return household.value?.preferences?.firstDayOfWeek || 0;
});
// Adapted From https://github.com/SortableJS/Vue.Draggable/issues/1029
const ogEvent: DragEvent = (evt as any).originalEvent;
// Local mutable meals object
const mealplansByDate = reactive<{ [date: string]: UpdatePlanEntry[] }>({});
watch(
() => props.mealplans,
(plans) => {
for (const plan of plans) {
mealplansByDate[plan.date.toString()] = plan.meals ? [...plan.meals] : [];
}
// Remove any dates that no longer exist
Object.keys(mealplansByDate).forEach((date) => {
if (!plans.find(p => p.date.toString() === date)) {
mealplansByDate[date] = [];
}
});
},
{ immediate: true, deep: true },
);
if (ogEvent && ogEvent.type in supportedEvents) {
// The drop was cancelled, unsure if anything needs to be done?
console.log("Cancel Move Event");
}
else {
// A Meal was moved, set the new date value and make an update request and refresh the meals
const fromMealsByIndex = parseInt(evt.from.getAttribute("data-index") ?? "");
const toMealsByIndex = parseInt(evt.to.getAttribute("data-index") ?? "");
function onMoveCallback(evt: SortableEvent) {
const supportedEvents = ["drop", "touchend"];
if (!isNaN(fromMealsByIndex) && !isNaN(toMealsByIndex)) {
const destDate = props.mealplans[toMealsByIndex].date;
const mealData = mealplansByDate[destDate.toString()][evt.newIndex as number];
// Adapted From https://github.com/SortableJS/Vue.Draggable/issues/1029
const ogEvent: DragEvent = (evt as any).originalEvent;
mealData.date = format(destDate, "yyyy-MM-dd");
if (ogEvent && ogEvent.type in supportedEvents) {
// The drop was cancelled, unsure if anything needs to be done?
console.log("Cancel Move Event");
}
else {
// A Meal was moved, set the new date value and make an update request and refresh the meals
const fromMealsByIndex = parseInt(evt.from.getAttribute("data-index") ?? "");
const toMealsByIndex = parseInt(evt.to.getAttribute("data-index") ?? "");
if (!isNaN(fromMealsByIndex) && !isNaN(toMealsByIndex)) {
const destDate = props.mealplans[toMealsByIndex].date;
const mealData = mealplansByDate[destDate.toString()][evt.newIndex as number];
mealData.date = format(destDate, "yyyy-MM-dd");
props.actions.updateOne(mealData);
}
}
props.actions.updateOne(mealData);
}
}
}
// =====================================================
// New Meal Dialog
// =====================================================
// New Meal Dialog
const dialog = reactive({
loading: false,
error: false,
note: false,
});
const dialog = reactive({
loading: false,
error: false,
note: false,
});
watch(dialog, () => {
if (dialog.note) {
newMeal.recipeId = undefined;
}
});
watch(dialog, () => {
if (dialog.note) {
newMeal.recipeId = undefined;
}
});
const newMeal = reactive({
date: new Date(Date.now() - new Date().getTimezoneOffset() * 60000),
title: "",
text: "",
recipeId: undefined as string | undefined,
entryType: "dinner" as PlanEntryType,
existing: false,
id: 0,
groupId: "",
userId: auth.user.value?.id || "",
});
const newMeal = reactive({
date: new Date(Date.now() - new Date().getTimezoneOffset() * 60000),
title: "",
text: "",
recipeId: undefined as string | undefined,
entryType: "dinner" as PlanEntryType,
existing: false,
id: 0,
groupId: "",
userId: auth.user.value?.id || "",
});
const newMealDateString = computed(() => {
return format(newMeal.date, "yyyy-MM-dd");
});
const newMealDateString = computed(() => {
return format(newMeal.date, "yyyy-MM-dd");
});
const isCreateDisabled = computed(() => {
if (dialog.note) {
return !newMeal.title.trim();
}
return !newMeal.recipeId;
});
const isCreateDisabled = computed(() => {
if (dialog.note) {
return !newMeal.title.trim();
}
return !newMeal.recipeId;
});
function openDialog(date: Date) {
newMeal.date = date;
state.value.dialog = true;
}
function openDialog(date: Date) {
newMeal.date = date;
state.value.dialog = true;
}
function editMeal(mealplan: UpdatePlanEntry) {
const { date, title, text, entryType, recipeId, id, groupId, userId } = mealplan;
if (!entryType) return;
function editMeal(mealplan: UpdatePlanEntry) {
const { date, title, text, entryType, recipeId, id, groupId, userId } = mealplan;
if (!entryType) return;
const [year, month, day] = date.split("-").map(Number);
newMeal.date = new Date(year, month - 1, day);
newMeal.title = title || "";
newMeal.text = text || "";
newMeal.recipeId = recipeId || undefined;
newMeal.entryType = entryType;
newMeal.existing = true;
newMeal.id = id;
newMeal.groupId = groupId;
newMeal.userId = userId || auth.user.value?.id || "";
const [year, month, day] = date.split("-").map(Number);
newMeal.date = new Date(year, month - 1, day);
newMeal.title = title || "";
newMeal.text = text || "";
newMeal.recipeId = recipeId || undefined;
newMeal.entryType = entryType;
newMeal.existing = true;
newMeal.id = id;
newMeal.groupId = groupId;
newMeal.userId = userId || auth.user.value?.id || "";
state.value.dialog = true;
dialog.note = !recipeId;
}
state.value.dialog = true;
dialog.note = !recipeId;
}
function resetDialog() {
newMeal.date = new Date(Date.now() - new Date().getTimezoneOffset() * 60000);
newMeal.title = "";
newMeal.text = "";
newMeal.entryType = "dinner";
newMeal.recipeId = undefined;
newMeal.existing = false;
}
function resetDialog() {
newMeal.date = new Date(Date.now() - new Date().getTimezoneOffset() * 60000);
newMeal.title = "";
newMeal.text = "";
newMeal.entryType = "dinner";
newMeal.recipeId = undefined;
newMeal.existing = false;
}
async function randomMeal(date: Date, type: PlanEntryType) {
const { data } = await api.mealplans.setRandom({
date: format(date, "yyyy-MM-dd"),
entryType: type,
});
async function randomMeal(date: Date, type: PlanEntryType) {
const { data } = await api.mealplans.setRandom({
date: format(date, "yyyy-MM-dd"),
entryType: type,
});
if (data) {
props.actions.refreshAll();
}
}
if (data) {
props.actions.refreshAll();
}
}
// =====================================================
// Search
// =====================================================
// Search
const search = useRecipeSearch(api);
const planTypeOptions = usePlanTypeOptions();
const search = useRecipeSearch(api);
const planTypeOptions = usePlanTypeOptions();
onMounted(async () => {
await search.trigger();
});
return {
state,
onMoveCallback,
planTypeOptions,
getEntryTypeText,
requiredRule,
isCreateDisabled,
normalizeFilter,
// Dialog
dialog,
newMeal,
newMealDateString,
openDialog,
editMeal,
resetDialog,
randomMeal,
// Search
search,
firstDayOfWeek,
mealplansByDate,
};
},
onMounted(async () => {
await search.trigger();
});
</script>

View File

@@ -50,7 +50,7 @@
</v-container>
</template>
<script lang="ts" setup>
<script setup lang="ts">
import type { MealsByDate } from "./types";
import type { ReadPlanEntry } from "~/lib/api/types/meal-plan";
import GroupMealPlanDayContextMenu from "~/components/Domain/Household/GroupMealPlanDayContextMenu.vue";

View File

@@ -171,102 +171,77 @@
</v-container>
</template>
<script lang="ts">
<script setup lang="ts">
import { useUserApi } from "~/composables/api";
import type { PlanRulesCreate, PlanRulesOut } from "~/lib/api/types/meal-plan";
import GroupMealPlanRuleForm from "~/components/Domain/Household/GroupMealPlanRuleForm.vue";
import { useAsyncKey } from "~/composables/use-utils";
import RecipeChips from "~/components/Domain/Recipe/RecipeChips.vue";
export default defineNuxtComponent({
components: {
GroupMealPlanRuleForm,
RecipeChips,
},
props: {
modelValue: {
type: Boolean,
default: false,
},
},
setup() {
const api = useUserApi();
const i18n = useI18n();
const api = useUserApi();
const i18n = useI18n();
useSeoMeta({
title: i18n.t("meal-plan.meal-plan-settings"),
});
useSeoMeta({
title: i18n.t("meal-plan.meal-plan-settings"),
});
// ======================================================
// Manage All
const editState = ref<{ [key: string]: boolean }>({});
const allRules = ref<PlanRulesOut[]>([]);
// ======================================================
// Manage All
const editState = ref<{ [key: string]: boolean }>({});
const allRules = ref<PlanRulesOut[]>([]);
function toggleEditState(id: string) {
editState.value[id] = !editState.value[id];
editState.value = { ...editState.value };
}
function toggleEditState(id: string) {
editState.value[id] = !editState.value[id];
editState.value = { ...editState.value };
}
async function refreshAll() {
const { data } = await api.mealplanRules.getAll();
async function refreshAll() {
const { data } = await api.mealplanRules.getAll();
if (data) {
allRules.value = data.items ?? [];
}
}
if (data) {
allRules.value = data.items ?? [];
}
}
useAsyncData(useAsyncKey(), async () => {
await refreshAll();
});
useAsyncData(useAsyncKey(), async () => {
await refreshAll();
});
// ======================================================
// Creating Rules
// ======================================================
// Creating Rules
const createDataFormKey = ref(0);
const createData = ref<PlanRulesCreate>({
const createDataFormKey = ref(0);
const createData = ref<PlanRulesCreate>({
entryType: "unset",
day: "unset",
queryFilterString: "",
});
async function createRule() {
const { data } = await api.mealplanRules.createOne(createData.value);
if (data) {
refreshAll();
createData.value = {
entryType: "unset",
day: "unset",
queryFilterString: "",
});
async function createRule() {
const { data } = await api.mealplanRules.createOne(createData.value);
if (data) {
refreshAll();
createData.value = {
entryType: "unset",
day: "unset",
queryFilterString: "",
};
createDataFormKey.value++;
}
}
async function deleteRule(ruleId: string) {
const { data } = await api.mealplanRules.deleteOne(ruleId);
if (data) {
refreshAll();
}
}
async function updateRule(rule: PlanRulesOut) {
const { data } = await api.mealplanRules.updateOne(rule.id, rule);
if (data) {
refreshAll();
toggleEditState(rule.id);
}
}
return {
allRules,
createDataFormKey,
createData,
createRule,
deleteRule,
editState,
updateRule,
toggleEditState,
};
},
});
createDataFormKey.value++;
}
}
async function deleteRule(ruleId: string) {
const { data } = await api.mealplanRules.deleteOne(ruleId);
if (data) {
refreshAll();
}
}
async function updateRule(rule: PlanRulesOut) {
const { data } = await api.mealplanRules.updateOne(rule.id, rule);
if (data) {
refreshAll();
toggleEditState(rule.id);
}
}
</script>

View File

@@ -115,61 +115,51 @@
</v-container>
</template>
<script lang="ts">
<script setup lang="ts">
import { useUserApi } from "~/composables/api";
import type { UserOut } from "~/lib/api/types/user";
import UserAvatar from "~/components/Domain/User/UserAvatar.vue";
export default defineNuxtComponent({
components: {
UserAvatar,
},
setup() {
const auth = useMealieAuth();
const api = useUserApi();
const i18n = useI18n();
const api = useUserApi();
const i18n = useI18n();
useSeoMeta({
title: i18n.t("profile.members"),
});
useSeoMeta({
title: i18n.t("profile.members"),
});
const members = ref<UserOut[] | null[]>([]);
const members = ref<UserOut[] | null[]>([]);
const headers = [
{ title: "", value: "avatar", sortable: false, align: "center" },
{ title: i18n.t("user.username"), value: "username" },
{ title: i18n.t("user.full-name"), value: "fullName" },
{ title: i18n.t("user.admin"), value: "admin" },
{ title: i18n.t("group.manage"), value: "manage", sortable: false, align: "center" },
{ title: i18n.t("settings.organize"), value: "organize", sortable: false, align: "center" },
{ title: i18n.t("group.invite"), value: "invite", sortable: false, align: "center" },
{ title: i18n.t("group.manage-household"), value: "manageHousehold", sortable: false, align: "center" },
];
const headers = [
{ title: "", value: "avatar", sortable: false, align: "center" },
{ title: i18n.t("user.username"), value: "username" },
{ title: i18n.t("user.full-name"), value: "fullName" },
{ title: i18n.t("user.admin"), value: "admin" },
{ title: i18n.t("group.manage"), value: "manage", sortable: false, align: "center" },
{ title: i18n.t("settings.organize"), value: "organize", sortable: false, align: "center" },
{ title: i18n.t("group.invite"), value: "invite", sortable: false, align: "center" },
{ title: i18n.t("group.manage-household"), value: "manageHousehold", sortable: false, align: "center" },
];
async function refreshMembers() {
const { data } = await api.households.fetchMembers();
if (data) {
members.value = data.items;
}
}
async function refreshMembers() {
const { data } = await api.households.fetchMembers();
if (data) {
members.value = data.items;
}
}
async function setPermissions(user: UserOut) {
const payload = {
userId: user.id,
canInvite: user.canInvite,
canManageHousehold: user.canManageHousehold,
canManage: user.canManage,
canOrganize: user.canOrganize,
};
async function setPermissions(user: UserOut) {
const payload = {
userId: user.id,
canInvite: user.canInvite,
canManageHousehold: user.canManageHousehold,
canManage: user.canManage,
canOrganize: user.canOrganize,
};
await api.households.setMemberPermissions(payload);
}
await api.households.setMemberPermissions(payload);
}
onMounted(async () => {
await refreshMembers();
});
return { members, headers, setPermissions, sessionUser: auth.user };
},
onMounted(async () => {
await refreshMembers();
});
</script>

View File

@@ -1,19 +1,19 @@
<template>
<v-container class="narrow-container">
<BaseDialog
v-model="deleteDialog"
v-model="state.deleteDialog"
color="error"
:title="$t('general.confirm')"
:icon="$globals.icons.alertCircle"
can-confirm
@confirm="deleteNotifier(deleteTargetId)"
@confirm="deleteNotifier(state.deleteTargetId)"
>
<v-card-text>
{{ $t("general.confirm-delete-generic") }}
</v-card-text>
</BaseDialog>
<BaseDialog
v-model="createDialog"
v-model="state.createDialog"
:title="$t('events.new-notification')"
:icon="$globals.icons.bellPlus"
can-submit
@@ -83,7 +83,7 @@
<BaseButton
create
@click="createDialog = true"
@click="state.createDialog = true"
/>
<v-expansion-panels
v-if="notifiers"
@@ -182,7 +182,7 @@
</v-container>
</template>
<script lang="ts">
<script setup lang="ts">
import { useUserApi } from "~/composables/api";
import { useAsyncKey } from "~/composables/use-utils";
import type { GroupEventNotifierCreate, GroupEventNotifierOut } from "~/lib/api/types/household";
@@ -198,210 +198,197 @@ interface OptionSection {
options: OptionKey[];
}
export default defineNuxtComponent({
definePageMeta({
middleware: ["advanced-only"],
setup() {
const api = useUserApi();
const i18n = useI18n();
useSeoMeta({
title: i18n.t("profile.notifiers"),
});
const state = reactive({
deleteDialog: false,
createDialog: false,
deleteTargetId: "",
});
const { data: notifiers } = useAsyncData(useAsyncKey(), async () => {
const { data } = await api.groupEventNotifier.getAll();
return data?.items;
});
async function refreshNotifiers() {
const { data } = await api.groupEventNotifier.getAll();
notifiers.value = data?.items;
}
const createNotifierData: GroupEventNotifierCreate = reactive({
name: "",
enabled: true,
appriseUrl: "",
});
async function createNewNotifier() {
await api.groupEventNotifier.createOne(createNotifierData);
refreshNotifiers();
}
function openDelete(notifier: GroupEventNotifierOut) {
state.deleteDialog = true;
state.deleteTargetId = notifier.id;
}
async function deleteNotifier(targetId: string) {
await api.groupEventNotifier.deleteOne(targetId);
refreshNotifiers();
state.deleteTargetId = "";
}
async function saveNotifier(notifier: GroupEventNotifierOut) {
await api.groupEventNotifier.updateOne(notifier.id, notifier);
refreshNotifiers();
}
async function testNotifier(notifier: GroupEventNotifierOut) {
await api.groupEventNotifier.test(notifier.id);
}
// ===============================================================
// Options Definitions
const optionsSections: OptionSection[] = [
{
id: 1,
text: i18n.t("events.recipe-events"),
options: [
{
text: i18n.t("general.create") as string,
key: "recipeCreated",
},
{
text: i18n.t("general.update") as string,
key: "recipeUpdated",
},
{
text: i18n.t("general.delete") as string,
key: "recipeDeleted",
},
],
},
{
id: 2,
text: i18n.t("events.user-events"),
options: [
{
text: i18n.t("events.when-a-new-user-joins-your-group"),
key: "userSignup",
},
],
},
{
id: 3,
text: i18n.t("events.mealplan-events"),
options: [
{
text: i18n.t("events.when-a-user-in-your-group-creates-a-new-mealplan"),
key: "mealplanEntryCreated",
},
],
},
{
id: 4,
text: i18n.t("events.shopping-list-events"),
options: [
{
text: i18n.t("general.create") as string,
key: "shoppingListCreated",
},
{
text: i18n.t("general.update") as string,
key: "shoppingListUpdated",
},
{
text: i18n.t("general.delete") as string,
key: "shoppingListDeleted",
},
],
},
{
id: 5,
text: i18n.t("events.cookbook-events"),
options: [
{
text: i18n.t("general.create") as string,
key: "cookbookCreated",
},
{
text: i18n.t("general.update") as string,
key: "cookbookUpdated",
},
{
text: i18n.t("general.delete") as string,
key: "cookbookDeleted",
},
],
},
{
id: 6,
text: i18n.t("events.tag-events"),
options: [
{
text: i18n.t("general.create") as string,
key: "tagCreated",
},
{
text: i18n.t("general.update") as string,
key: "tagUpdated",
},
{
text: i18n.t("general.delete") as string,
key: "tagDeleted",
},
],
},
{
id: 7,
text: i18n.t("events.category-events"),
options: [
{
text: i18n.t("general.create") as string,
key: "categoryCreated",
},
{
text: i18n.t("general.update") as string,
key: "categoryUpdated",
},
{
text: i18n.t("general.delete") as string,
key: "categoryDeleted",
},
],
},
{
id: 8,
text: i18n.t("events.label-events"),
options: [
{
text: i18n.t("general.create") as string,
key: "labelCreated",
},
{
text: i18n.t("general.update") as string,
key: "labelUpdated",
},
{
text: i18n.t("general.delete") as string,
key: "labelDeleted",
},
],
},
];
return {
...toRefs(state),
openDelete,
notifiers,
createNotifierData,
optionsSections,
deleteNotifier,
testNotifier,
saveNotifier,
createNewNotifier,
};
},
});
const api = useUserApi();
const i18n = useI18n();
useSeoMeta({
title: i18n.t("profile.notifiers"),
});
const state = reactive({
deleteDialog: false,
createDialog: false,
deleteTargetId: "",
});
const { data: notifiers } = useAsyncData(useAsyncKey(), async () => {
const { data } = await api.groupEventNotifier.getAll();
return data?.items;
});
async function refreshNotifiers() {
const { data } = await api.groupEventNotifier.getAll();
notifiers.value = data?.items;
}
const createNotifierData: GroupEventNotifierCreate = reactive({
name: "",
enabled: true,
appriseUrl: "",
});
async function createNewNotifier() {
await api.groupEventNotifier.createOne(createNotifierData);
refreshNotifiers();
}
function openDelete(notifier: GroupEventNotifierOut) {
state.deleteDialog = true;
state.deleteTargetId = notifier.id;
}
async function deleteNotifier(targetId: string) {
await api.groupEventNotifier.deleteOne(targetId);
refreshNotifiers();
state.deleteTargetId = "";
}
async function saveNotifier(notifier: GroupEventNotifierOut) {
await api.groupEventNotifier.updateOne(notifier.id, notifier);
refreshNotifiers();
}
async function testNotifier(notifier: GroupEventNotifierOut) {
await api.groupEventNotifier.test(notifier.id);
}
// ===============================================================
// Options Definitions
const optionsSections: OptionSection[] = [
{
id: 1,
text: i18n.t("events.recipe-events"),
options: [
{
text: i18n.t("general.create") as string,
key: "recipeCreated",
},
{
text: i18n.t("general.update") as string,
key: "recipeUpdated",
},
{
text: i18n.t("general.delete") as string,
key: "recipeDeleted",
},
],
},
{
id: 2,
text: i18n.t("events.user-events"),
options: [
{
text: i18n.t("events.when-a-new-user-joins-your-group"),
key: "userSignup",
},
],
},
{
id: 3,
text: i18n.t("events.mealplan-events"),
options: [
{
text: i18n.t("events.when-a-user-in-your-group-creates-a-new-mealplan"),
key: "mealplanEntryCreated",
},
],
},
{
id: 4,
text: i18n.t("events.shopping-list-events"),
options: [
{
text: i18n.t("general.create") as string,
key: "shoppingListCreated",
},
{
text: i18n.t("general.update") as string,
key: "shoppingListUpdated",
},
{
text: i18n.t("general.delete") as string,
key: "shoppingListDeleted",
},
],
},
{
id: 5,
text: i18n.t("events.cookbook-events"),
options: [
{
text: i18n.t("general.create") as string,
key: "cookbookCreated",
},
{
text: i18n.t("general.update") as string,
key: "cookbookUpdated",
},
{
text: i18n.t("general.delete") as string,
key: "cookbookDeleted",
},
],
},
{
id: 6,
text: i18n.t("events.tag-events"),
options: [
{
text: i18n.t("general.create") as string,
key: "tagCreated",
},
{
text: i18n.t("general.update") as string,
key: "tagUpdated",
},
{
text: i18n.t("general.delete") as string,
key: "tagDeleted",
},
],
},
{
id: 7,
text: i18n.t("events.category-events"),
options: [
{
text: i18n.t("general.create") as string,
key: "categoryCreated",
},
{
text: i18n.t("general.update") as string,
key: "categoryUpdated",
},
{
text: i18n.t("general.delete") as string,
key: "categoryDeleted",
},
],
},
{
id: 8,
text: i18n.t("events.label-events"),
options: [
{
text: i18n.t("general.create") as string,
key: "labelCreated",
},
{
text: i18n.t("general.update") as string,
key: "labelUpdated",
},
{
text: i18n.t("general.delete") as string,
key: "labelDeleted",
},
],
},
];
</script>
<style>

View File

@@ -68,28 +68,19 @@
</v-container>
</template>
<script lang="ts">
<script setup lang="ts">
import { useGroupWebhooks, timeUTC } from "~/composables/use-group-webhooks";
import GroupWebhookEditor from "~/components/Domain/Household/GroupWebhookEditor.vue";
import { alert } from "~/composables/use-toast";
export default defineNuxtComponent({
components: { GroupWebhookEditor },
definePageMeta({
middleware: ["advanced-only"],
setup() {
const i18n = useI18n();
const { actions, webhooks } = useGroupWebhooks();
});
useSeoMeta({
title: i18n.t("settings.webhooks.webhooks"),
});
const i18n = useI18n();
const { actions, webhooks } = useGroupWebhooks();
return {
alert,
webhooks,
actions,
timeUTC,
};
},
useSeoMeta({
title: i18n.t("settings.webhooks.webhooks"),
});
</script>