| 
									
										
										
										
											2022-06-03 20:12:32 -08:00
										 |  |  | <template> | 
					
						
							|  |  |  |   <div v-if="items"> | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |     <RecipeOrganizerDialog | 
					
						
							|  |  |  |       v-model="dialogs.organizer" | 
					
						
							|  |  |  |       :item-type="itemType" | 
					
						
							|  |  |  |     /> | 
					
						
							| 
									
										
										
										
											2022-06-03 20:12:32 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     <BaseDialog | 
					
						
							|  |  |  |       v-if="deleteTarget" | 
					
						
							| 
									
										
										
										
											2023-03-12 12:59:28 -08:00
										 |  |  |       v-model="dialogs.delete" | 
					
						
							| 
									
										
										
										
											2024-01-04 11:44:04 +00:00
										 |  |  |       :title="$t('general.delete-with-name', { name: $t(translationKey) })" | 
					
						
							| 
									
										
										
										
											2022-06-03 20:12:32 -08:00
										 |  |  |       color="error" | 
					
						
							|  |  |  |       :icon="$globals.icons.alertCircle" | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |       can-confirm | 
					
						
							| 
									
										
										
										
											2022-06-03 20:12:32 -08:00
										 |  |  |       @confirm="deleteOne()" | 
					
						
							|  |  |  |     > | 
					
						
							| 
									
										
										
										
											2024-01-04 11:44:04 +00:00
										 |  |  |       <v-card-text> | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |         <p>{{ $t("general.confirm-delete-generic-with-name", { name: $t(translationKey) }) }}</p> | 
					
						
							|  |  |  |         <p class="mt-4 mb-0 ml-4"> | 
					
						
							|  |  |  |           {{ deleteTarget.name }} | 
					
						
							|  |  |  |         </p> | 
					
						
							| 
									
										
										
										
											2024-01-04 11:44:04 +00:00
										 |  |  |       </v-card-text> | 
					
						
							| 
									
										
										
										
											2022-06-03 20:12:32 -08:00
										 |  |  |     </BaseDialog> | 
					
						
							| 
									
										
										
										
											2022-12-01 06:31:16 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |     <BaseDialog | 
					
						
							|  |  |  |       v-if="updateTarget" | 
					
						
							|  |  |  |       v-model="dialogs.update" | 
					
						
							|  |  |  |       :title="$t('general.update')" | 
					
						
							|  |  |  |       can-confirm | 
					
						
							|  |  |  |       @confirm="updateOne()" | 
					
						
							|  |  |  |     > | 
					
						
							| 
									
										
										
										
											2023-03-12 12:59:28 -08:00
										 |  |  |       <v-card-text> | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |         <v-text-field | 
					
						
							|  |  |  |           v-model="updateTarget.name" | 
					
						
							|  |  |  |           :label="$t('general.name')" | 
					
						
							|  |  |  |         /> | 
					
						
							|  |  |  |         <v-checkbox | 
					
						
							|  |  |  |           v-if="itemType === Organizer.Tool" | 
					
						
							|  |  |  |           v-model="updateTarget.onHand" | 
					
						
							|  |  |  |           :label="$t('tool.on-hand')" | 
					
						
							|  |  |  |         /> | 
					
						
							| 
									
										
										
										
											2023-03-12 12:59:28 -08:00
										 |  |  |       </v-card-text> | 
					
						
							|  |  |  |     </BaseDialog> | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-01 06:31:16 +01:00
										 |  |  |     <v-row dense> | 
					
						
							| 
									
										
										
										
											2023-03-12 12:59:28 -08:00
										 |  |  |       <v-col> | 
					
						
							|  |  |  |         <v-text-field | 
					
						
							|  |  |  |           v-model="searchString" | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |           variant="outlined" | 
					
						
							| 
									
										
										
										
											2023-03-12 12:59:28 -08:00
										 |  |  |           autofocus | 
					
						
							|  |  |  |           color="primary accent-3" | 
					
						
							|  |  |  |           :placeholder="$t('search.search-placeholder')" | 
					
						
							|  |  |  |           :prepend-inner-icon="$globals.icons.search" | 
					
						
							|  |  |  |           clearable | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |         /> | 
					
						
							| 
									
										
										
										
											2023-03-12 12:59:28 -08:00
										 |  |  |       </v-col> | 
					
						
							|  |  |  |     </v-row> | 
					
						
							| 
									
										
										
										
											2022-12-01 06:31:16 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |     <v-app-bar | 
					
						
							|  |  |  |       color="transparent" | 
					
						
							|  |  |  |       flat | 
					
						
							| 
									
										
										
										
											2025-06-28 15:59:58 +02:00
										 |  |  |       class="mt-n1 rounded align-center position-relative w-100 left-0 top-0" | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |     > | 
					
						
							|  |  |  |       <v-icon | 
					
						
							|  |  |  |         size="large" | 
					
						
							|  |  |  |         start | 
					
						
							|  |  |  |       > | 
					
						
							| 
									
										
										
										
											2022-06-03 20:12:32 -08:00
										 |  |  |         {{ icon }} | 
					
						
							|  |  |  |       </v-icon> | 
					
						
							|  |  |  |       <v-toolbar-title class="headline"> | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |         <slot name="title" /> | 
					
						
							| 
									
										
										
										
											2022-06-03 20:12:32 -08:00
										 |  |  |       </v-toolbar-title> | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |       <v-spacer /> | 
					
						
							|  |  |  |       <BaseButton | 
					
						
							|  |  |  |         create | 
					
						
							|  |  |  |         @click="dialogs.organizer = true" | 
					
						
							|  |  |  |       /> | 
					
						
							| 
									
										
										
										
											2022-06-03 20:12:32 -08:00
										 |  |  |     </v-app-bar> | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |     <section | 
					
						
							|  |  |  |       v-for="(itms, key, idx) in itemsSorted" | 
					
						
							|  |  |  |       :key="'header' + idx" | 
					
						
							|  |  |  |       :class="idx === 1 ? null : 'my-4'" | 
					
						
							|  |  |  |     > | 
					
						
							|  |  |  |       <BaseCardSectionTitle | 
					
						
							|  |  |  |         v-if="isTitle(key)" | 
					
						
							|  |  |  |         :title="key" | 
					
						
							|  |  |  |       /> | 
					
						
							| 
									
										
										
										
											2022-06-03 20:12:32 -08:00
										 |  |  |       <v-row> | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |         <v-col | 
					
						
							|  |  |  |           v-for="(item, index) in itms" | 
					
						
							|  |  |  |           :key="'cat' + index" | 
					
						
							|  |  |  |           cols="12" | 
					
						
							|  |  |  |           :sm="12" | 
					
						
							|  |  |  |           :md="6" | 
					
						
							|  |  |  |           :lg="4" | 
					
						
							|  |  |  |           :xl="3" | 
					
						
							|  |  |  |         > | 
					
						
							|  |  |  |           <v-card | 
					
						
							|  |  |  |             v-if="item" | 
					
						
							|  |  |  |             class="left-border" | 
					
						
							|  |  |  |             hover | 
					
						
							|  |  |  |             :to="`/g/${groupSlug}?${itemType}=${item.id}`" | 
					
						
							|  |  |  |           > | 
					
						
							| 
									
										
										
										
											2022-06-03 20:12:32 -08:00
										 |  |  |             <v-card-actions> | 
					
						
							|  |  |  |               <v-icon> | 
					
						
							|  |  |  |                 {{ icon }} | 
					
						
							|  |  |  |               </v-icon> | 
					
						
							|  |  |  |               <v-card-title class="py-1"> | 
					
						
							|  |  |  |                 {{ item.name }} | 
					
						
							|  |  |  |               </v-card-title> | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |               <v-spacer /> | 
					
						
							| 
									
										
										
										
											2023-03-12 12:59:28 -08:00
										 |  |  |               <ContextMenu | 
					
						
							|  |  |  |                 :items="[presets.delete, presets.edit]" | 
					
						
							|  |  |  |                 @delete="confirmDelete(item)" | 
					
						
							|  |  |  |                 @edit="openUpdateDialog(item)" | 
					
						
							|  |  |  |               /> | 
					
						
							| 
									
										
										
										
											2022-06-03 20:12:32 -08:00
										 |  |  |             </v-card-actions> | 
					
						
							|  |  |  |           </v-card> | 
					
						
							|  |  |  |         </v-col> | 
					
						
							|  |  |  |       </v-row> | 
					
						
							|  |  |  |     </section> | 
					
						
							|  |  |  |   </div> | 
					
						
							|  |  |  | </template> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <script lang="ts"> | 
					
						
							| 
									
										
										
										
											2022-12-01 06:31:16 +01:00
										 |  |  | import Fuse from "fuse.js"; | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-03 20:12:32 -08:00
										 |  |  | import { useContextPresets } from "~/composables/use-context-presents"; | 
					
						
							|  |  |  | import RecipeOrganizerDialog from "~/components/Domain/Recipe/RecipeOrganizerDialog.vue"; | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  | import { Organizer, type RecipeOrganizer } from "~/lib/api/types/non-generated"; | 
					
						
							| 
									
										
										
										
											2022-12-01 06:31:16 +01:00
										 |  |  | import { useRouteQuery } from "~/composables/use-router"; | 
					
						
							| 
									
										
										
										
											2023-03-12 12:59:28 -08:00
										 |  |  | import { deepCopy } from "~/composables/use-utils"; | 
					
						
							| 
									
										
										
										
											2022-06-03 20:12:32 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | interface GenericItem { | 
					
						
							| 
									
										
										
										
											2023-03-12 12:59:28 -08:00
										 |  |  |   id: string; | 
					
						
							| 
									
										
										
										
											2022-06-03 20:12:32 -08:00
										 |  |  |   name: string; | 
					
						
							|  |  |  |   slug: string; | 
					
						
							| 
									
										
										
										
											2023-11-21 15:31:05 +01:00
										 |  |  |   onHand: boolean; | 
					
						
							| 
									
										
										
										
											2022-06-03 20:12:32 -08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  | export default defineNuxtComponent({ | 
					
						
							| 
									
										
										
										
											2022-06-03 20:12:32 -08:00
										 |  |  |   components: { | 
					
						
							|  |  |  |     RecipeOrganizerDialog, | 
					
						
							|  |  |  |   }, | 
					
						
							|  |  |  |   props: { | 
					
						
							|  |  |  |     items: { | 
					
						
							|  |  |  |       type: Array as () => GenericItem[], | 
					
						
							|  |  |  |       required: true, | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     icon: { | 
					
						
							|  |  |  |       type: String, | 
					
						
							|  |  |  |       required: true, | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     itemType: { | 
					
						
							|  |  |  |       type: String as () => RecipeOrganizer, | 
					
						
							|  |  |  |       required: true, | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |   }, | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |   emits: ["update", "delete"], | 
					
						
							| 
									
										
										
										
											2022-06-03 20:12:32 -08:00
										 |  |  |   setup(props, { emit }) { | 
					
						
							| 
									
										
										
										
											2022-12-01 06:31:16 +01:00
										 |  |  |     const state = reactive({ | 
					
						
							|  |  |  |       // Search Options
 | 
					
						
							|  |  |  |       options: { | 
					
						
							|  |  |  |         ignoreLocation: true, | 
					
						
							|  |  |  |         shouldSort: true, | 
					
						
							| 
									
										
										
										
											2024-07-27 23:23:26 -05:00
										 |  |  |         threshold: 0.2, | 
					
						
							| 
									
										
										
										
											2022-12-01 06:31:16 +01:00
										 |  |  |         location: 0, | 
					
						
							| 
									
										
										
										
											2024-07-27 23:23:26 -05:00
										 |  |  |         distance: 20, | 
					
						
							| 
									
										
										
										
											2022-12-01 06:31:16 +01:00
										 |  |  |         findAllMatches: true, | 
					
						
							|  |  |  |         maxPatternLength: 32, | 
					
						
							|  |  |  |         minMatchCharLength: 1, | 
					
						
							|  |  |  |         keys: ["name"], | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2022-06-03 20:12:32 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |     const $auth = useMealieAuth(); | 
					
						
							| 
									
										
										
										
											2023-11-05 19:07:02 -06:00
										 |  |  |     const route = useRoute(); | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |     const groupSlug = computed(() => route.params.groupSlug as string || $auth.user?.value?.groupSlug || ""); | 
					
						
							| 
									
										
										
										
											2023-11-05 19:07:02 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-12 12:59:28 -08:00
										 |  |  |     // =================================================================
 | 
					
						
							|  |  |  |     // Context Menu
 | 
					
						
							| 
									
										
										
										
											2022-06-03 20:12:32 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-12 12:59:28 -08:00
										 |  |  |     const dialogs = ref({ | 
					
						
							|  |  |  |       organizer: false, | 
					
						
							|  |  |  |       update: false, | 
					
						
							|  |  |  |       delete: false, | 
					
						
							| 
									
										
										
										
											2022-06-03 20:12:32 -08:00
										 |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const presets = useContextPresets(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-04 11:44:04 +00:00
										 |  |  |     const translationKey = computed<string>(() => { | 
					
						
							|  |  |  |       const typeMap = { | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |         categories: "category.category", | 
					
						
							|  |  |  |         tags: "tag.tag", | 
					
						
							|  |  |  |         tools: "tool.tool", | 
					
						
							|  |  |  |         foods: "shopping-list.food", | 
					
						
							|  |  |  |         households: "household.household", | 
					
						
							| 
									
										
										
										
											2024-01-04 11:44:04 +00:00
										 |  |  |       }; | 
					
						
							|  |  |  |       return typeMap[props.itemType] || ""; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-03 20:12:32 -08:00
										 |  |  |     const deleteTarget = ref<GenericItem | null>(null); | 
					
						
							| 
									
										
										
										
											2023-03-12 12:59:28 -08:00
										 |  |  |     const updateTarget = ref<GenericItem | null>(null); | 
					
						
							| 
									
										
										
										
											2022-06-03 20:12:32 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     function confirmDelete(item: GenericItem) { | 
					
						
							|  |  |  |       deleteTarget.value = item; | 
					
						
							| 
									
										
										
										
											2023-03-12 12:59:28 -08:00
										 |  |  |       dialogs.value.delete = true; | 
					
						
							| 
									
										
										
										
											2022-06-03 20:12:32 -08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     function deleteOne() { | 
					
						
							|  |  |  |       if (!deleteTarget.value) { | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       emit("delete", deleteTarget.value.id); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-12 12:59:28 -08:00
										 |  |  |     function openUpdateDialog(item: GenericItem) { | 
					
						
							|  |  |  |       updateTarget.value = deepCopy(item); | 
					
						
							|  |  |  |       dialogs.value.update = true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     function updateOne() { | 
					
						
							|  |  |  |       if (!updateTarget.value) { | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       emit("update", updateTarget.value); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-06-03 20:12:32 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-01 06:31:16 +01:00
										 |  |  |     // ================================================================
 | 
					
						
							|  |  |  |     // Search Functions
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const searchString = useRouteQuery("q", ""); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const fuse = computed(() => { | 
					
						
							|  |  |  |       return new Fuse(props.items, state.options); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-12 12:59:28 -08:00
										 |  |  |     const fuzzyItems = computed<GenericItem[]>(() => { | 
					
						
							| 
									
										
										
										
											2022-12-01 06:31:16 +01:00
										 |  |  |       if (searchString.value.trim() === "") { | 
					
						
							|  |  |  |         return props.items; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       const result = fuse.value.search(searchString.value.trim() as string); | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |       return result.map(x => x.item); | 
					
						
							| 
									
										
										
										
											2022-12-01 06:31:16 +01:00
										 |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-12 12:59:28 -08:00
										 |  |  |     // =================================================================
 | 
					
						
							|  |  |  |     // Sorted Items
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const itemsSorted = computed(() => { | 
					
						
							|  |  |  |       const byLetter: { [key: string]: Array<GenericItem> } = {}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (!fuzzyItems.value) { | 
					
						
							|  |  |  |         return byLetter; | 
					
						
							| 
									
										
										
										
											2022-12-01 06:31:16 +01:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2023-03-12 12:59:28 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |       [...fuzzyItems.value] | 
					
						
							| 
									
										
										
										
											2023-03-12 12:59:28 -08:00
										 |  |  |         .sort((a, b) => a.name.localeCompare(b.name)) | 
					
						
							|  |  |  |         .forEach((item) => { | 
					
						
							|  |  |  |           const letter = item.name[0].toUpperCase(); | 
					
						
							|  |  |  |           if (!byLetter[letter]) { | 
					
						
							|  |  |  |             byLetter[letter] = []; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |           byLetter[letter].push(item); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return byLetter; | 
					
						
							| 
									
										
										
										
											2022-12-01 06:31:16 +01:00
										 |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-12 12:59:28 -08:00
										 |  |  |     function isTitle(str: number | string) { | 
					
						
							|  |  |  |       return typeof str === "string" && str.length === 1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-03 20:12:32 -08:00
										 |  |  |     return { | 
					
						
							| 
									
										
										
										
											2023-11-05 19:07:02 -06:00
										 |  |  |       groupSlug, | 
					
						
							| 
									
										
										
										
											2023-03-12 12:59:28 -08:00
										 |  |  |       isTitle, | 
					
						
							|  |  |  |       dialogs, | 
					
						
							| 
									
										
										
										
											2022-06-03 20:12:32 -08:00
										 |  |  |       confirmDelete, | 
					
						
							| 
									
										
										
										
											2023-03-12 12:59:28 -08:00
										 |  |  |       openUpdateDialog, | 
					
						
							|  |  |  |       updateOne, | 
					
						
							|  |  |  |       updateTarget, | 
					
						
							| 
									
										
										
										
											2022-06-03 20:12:32 -08:00
										 |  |  |       deleteOne, | 
					
						
							|  |  |  |       deleteTarget, | 
					
						
							| 
									
										
										
										
											2023-11-21 15:31:05 +01:00
										 |  |  |       Organizer, | 
					
						
							| 
									
										
										
										
											2022-06-03 20:12:32 -08:00
										 |  |  |       presets, | 
					
						
							|  |  |  |       itemsSorted, | 
					
						
							| 
									
										
										
										
											2022-12-01 06:31:16 +01:00
										 |  |  |       searchString, | 
					
						
							| 
									
										
										
										
											2024-01-04 11:44:04 +00:00
										 |  |  |       translationKey, | 
					
						
							| 
									
										
										
										
											2022-06-03 20:12:32 -08:00
										 |  |  |     }; | 
					
						
							|  |  |  |   }, | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | </script> |