mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-10-27 16:24:31 -04:00
195 lines
5.3 KiB
TypeScript
195 lines
5.3 KiB
TypeScript
import type { ShoppingListItemOut } from "~/lib/api/types/household";
|
|
import { useShoppingListState } from "~/composables/shopping-list-page/sub-composables/use-shopping-list-state";
|
|
import { useShoppingListData } from "~/composables/shopping-list-page/sub-composables/use-shopping-list-data";
|
|
import { useShoppingListSorting } from "~/composables/shopping-list-page/sub-composables/use-shopping-list-sorting";
|
|
import { useShoppingListLabels } from "~/composables/shopping-list-page/sub-composables/use-shopping-list-labels";
|
|
import { useShoppingListCopy } from "~/composables/shopping-list-page/sub-composables/use-shopping-list-copy";
|
|
import { useShoppingListCrud } from "~/composables/shopping-list-page/sub-composables/use-shopping-list-crud";
|
|
import { useShoppingListRecipes } from "~/composables/shopping-list-page/sub-composables/use-shopping-list-recipes";
|
|
|
|
/**
|
|
* Main composable that orchestrates all shopping list page functionality
|
|
*/
|
|
export function useShoppingListPage(listId: string) {
|
|
// Initialize state
|
|
const state = useShoppingListState();
|
|
const {
|
|
shoppingList,
|
|
loadingCounter,
|
|
recipeReferenceLoading,
|
|
preserveItemOrder,
|
|
listItems,
|
|
sortCheckedItems,
|
|
} = state;
|
|
|
|
// Initialize sorting functionality
|
|
const sorting = useShoppingListSorting();
|
|
const { groupAndSortListItemsByFood, sortListItems, updateItemsByLabel } = sorting;
|
|
|
|
// Track items organized by label
|
|
const itemsByLabel = ref<{ [key: string]: ShoppingListItemOut[] }>({});
|
|
|
|
function updateListItemOrder() {
|
|
if (!shoppingList.value) return;
|
|
|
|
if (!preserveItemOrder.value) {
|
|
groupAndSortListItemsByFood(shoppingList.value);
|
|
}
|
|
else {
|
|
sortListItems(shoppingList.value);
|
|
}
|
|
|
|
const labeledItems = updateItemsByLabel(shoppingList.value);
|
|
if (labeledItems) {
|
|
itemsByLabel.value = labeledItems;
|
|
}
|
|
}
|
|
|
|
// Initialize data management
|
|
const dataManager = useShoppingListData(listId, shoppingList, loadingCounter);
|
|
const { isOffline, refresh: baseRefresh, startPolling, stopPolling, shoppingListItemActions } = dataManager;
|
|
|
|
const refresh = () => baseRefresh(updateListItemOrder);
|
|
|
|
// Initialize shopping list labels
|
|
const labels = useShoppingListLabels(shoppingList);
|
|
|
|
// Initialize copy functionality
|
|
const copyManager = useShoppingListCopy();
|
|
|
|
// Initialize CRUD operations
|
|
const crud = useShoppingListCrud(
|
|
shoppingList,
|
|
loadingCounter,
|
|
listItems,
|
|
shoppingListItemActions,
|
|
refresh,
|
|
sortCheckedItems,
|
|
updateListItemOrder,
|
|
);
|
|
|
|
// Initialize recipe management
|
|
const recipes = useShoppingListRecipes(
|
|
shoppingList,
|
|
loadingCounter,
|
|
recipeReferenceLoading,
|
|
refresh,
|
|
);
|
|
|
|
// Handle item reordering by label
|
|
function updateIndexUncheckedByLabel(labelName: string, labeledUncheckedItems: ShoppingListItemOut[]) {
|
|
if (!itemsByLabel.value[labelName]) {
|
|
return;
|
|
}
|
|
|
|
// update this label's item order
|
|
itemsByLabel.value[labelName] = labeledUncheckedItems;
|
|
|
|
// reset list order of all items
|
|
const allUncheckedItems: ShoppingListItemOut[] = [];
|
|
for (const labelKey in itemsByLabel.value) {
|
|
allUncheckedItems.push(...itemsByLabel.value[labelKey]);
|
|
}
|
|
|
|
// since the user has manually reordered the list, we should preserve this order
|
|
preserveItemOrder.value = true;
|
|
|
|
// save changes
|
|
listItems.unchecked = allUncheckedItems;
|
|
listItems.checked = shoppingList.value?.listItems?.filter(item => item.checked) || [];
|
|
crud.updateUncheckedListItems();
|
|
}
|
|
|
|
// Dialog helpers
|
|
function openCheckAll() {
|
|
if (shoppingList.value?.listItems?.some(item => !item.checked)) {
|
|
state.state.checkAllDialog = true;
|
|
}
|
|
}
|
|
|
|
function openUncheckAll() {
|
|
if (shoppingList.value?.listItems?.some(item => item.checked)) {
|
|
state.state.uncheckAllDialog = true;
|
|
}
|
|
}
|
|
|
|
function openDeleteChecked() {
|
|
if (shoppingList.value?.listItems?.some(item => item.checked)) {
|
|
state.state.deleteCheckedDialog = true;
|
|
}
|
|
}
|
|
|
|
function checkAll() {
|
|
state.state.checkAllDialog = false;
|
|
crud.checkAllItems();
|
|
}
|
|
|
|
function uncheckAll() {
|
|
state.state.uncheckAllDialog = false;
|
|
crud.uncheckAllItems();
|
|
}
|
|
|
|
function deleteChecked() {
|
|
state.state.deleteCheckedDialog = false;
|
|
crud.deleteCheckedItems();
|
|
}
|
|
|
|
// Copy functionality wrapper
|
|
function copyListItems(copyType: "plain" | "markdown") {
|
|
copyManager.copyListItems(itemsByLabel.value, copyType);
|
|
}
|
|
|
|
// Label reordering helpers
|
|
function toggleReorderLabelsDialog() {
|
|
crud.toggleReorderLabelsDialog(state.reorderLabelsDialog);
|
|
}
|
|
|
|
async function saveLabelOrder() {
|
|
await crud.saveLabelOrder(() => {
|
|
const labeledItems = updateItemsByLabel(shoppingList.value!);
|
|
if (labeledItems) {
|
|
itemsByLabel.value = labeledItems;
|
|
}
|
|
});
|
|
}
|
|
|
|
// Lifecycle management
|
|
onMounted(() => {
|
|
startPolling(updateListItemOrder);
|
|
});
|
|
|
|
onUnmounted(() => {
|
|
stopPolling();
|
|
});
|
|
|
|
return {
|
|
itemsByLabel,
|
|
isOffline,
|
|
|
|
// Sub-composables
|
|
...state,
|
|
...labels,
|
|
...crud,
|
|
...recipes,
|
|
|
|
// Specialized functions
|
|
updateIndexUncheckedByLabel,
|
|
copyListItems,
|
|
|
|
// Dialog actions
|
|
openCheckAll,
|
|
openUncheckAll,
|
|
openDeleteChecked,
|
|
checkAll,
|
|
uncheckAll,
|
|
deleteChecked,
|
|
|
|
// Label management
|
|
toggleReorderLabelsDialog,
|
|
saveLabelOrder,
|
|
|
|
// Data refresh
|
|
refresh,
|
|
};
|
|
}
|