mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2025-12-23 18:29:23 -05:00
shopping lists in SLE dialog
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
# Generated by Django 5.2.7 on 2025-11-30 14:00
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0232_shoppinglist'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='food',
|
||||
name='shopping_lists',
|
||||
field=models.ManyToManyField(blank=True, to='cookbook.shoppinglist'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='shoppinglistentry',
|
||||
name='shopping_lists',
|
||||
field=models.ManyToManyField(blank=True, to='cookbook.shoppinglist'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='supermarket',
|
||||
name='shopping_lists',
|
||||
field=models.ManyToManyField(blank=True, to='cookbook.shoppinglist'),
|
||||
),
|
||||
]
|
||||
@@ -666,6 +666,7 @@ class Supermarket(models.Model, PermissionModelMixin):
|
||||
name = models.CharField(max_length=128, validators=[MinLengthValidator(1)])
|
||||
description = models.TextField(blank=True, null=True)
|
||||
categories = models.ManyToManyField(SupermarketCategory, through='SupermarketCategoryRelation')
|
||||
shopping_lists = models.ManyToManyField("ShoppingList", blank=True)
|
||||
open_data_slug = models.CharField(max_length=128, null=True, blank=True, default=None)
|
||||
|
||||
space = models.ForeignKey(Space, on_delete=models.CASCADE)
|
||||
@@ -780,6 +781,7 @@ class Food(ExportModelOperationsMixin('food'), TreeModel, PermissionModelMixin):
|
||||
recipe = models.ForeignKey('Recipe', null=True, blank=True, on_delete=models.SET_NULL)
|
||||
url = models.CharField(max_length=1024, blank=True, null=True, default='')
|
||||
supermarket_category = models.ForeignKey(SupermarketCategory, null=True, blank=True, on_delete=models.SET_NULL) # inherited field
|
||||
shopping_lists = models.ManyToManyField("ShoppingList", blank=True)
|
||||
ignore_shopping = models.BooleanField(default=False) # inherited field
|
||||
onhand_users = models.ManyToManyField(User, blank=True)
|
||||
description = models.TextField(default='', blank=True)
|
||||
@@ -1317,6 +1319,7 @@ class ShoppingList(ExportModelOperationsMixin('shopping_list'), models.Model, Pe
|
||||
|
||||
|
||||
class ShoppingListEntry(ExportModelOperationsMixin('shopping_list_entry'), models.Model, PermissionModelMixin):
|
||||
shopping_lists = models.ManyToManyField(ShoppingList, blank=True)
|
||||
list_recipe = models.ForeignKey(ShoppingListRecipe, on_delete=models.CASCADE, null=True, blank=True, related_name='entries')
|
||||
food = models.ForeignKey(Food, on_delete=models.CASCADE, related_name='shopping_entries')
|
||||
unit = models.ForeignKey(Unit, on_delete=models.SET_NULL, null=True, blank=True)
|
||||
|
||||
@@ -1414,6 +1414,7 @@ class ShoppingListSerializer(SpacedModelSerializer, WritableNestedModelSerialize
|
||||
class ShoppingListEntrySerializer(WritableNestedModelSerializer):
|
||||
food = FoodSerializer(allow_null=True)
|
||||
unit = UnitSerializer(allow_null=True, required=False)
|
||||
shopping_lists = ShoppingListSerializer(many=True, required=False)
|
||||
list_recipe_data = ShoppingListRecipeSerializer(source='list_recipe', read_only=True)
|
||||
amount = CustomDecimalField()
|
||||
created_by = UserSerializer(read_only=True)
|
||||
@@ -1482,7 +1483,7 @@ class ShoppingListEntrySerializer(WritableNestedModelSerializer):
|
||||
class Meta:
|
||||
model = ShoppingListEntry
|
||||
fields = (
|
||||
'id', 'list_recipe', 'food', 'unit', 'amount', 'order', 'checked', 'ingredient',
|
||||
'id', 'list_recipe', 'shopping_lists', 'food', 'unit', 'amount', 'order', 'checked', 'ingredient',
|
||||
'list_recipe_data', 'created_by', 'created_at', 'updated_at', 'completed_at', 'delay_until', 'mealplan_id'
|
||||
)
|
||||
read_only_fields = ('id', 'created_by', 'created_at')
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
<v-label>{{ $t('Choose_Category') }}</v-label>
|
||||
<model-select model="SupermarketCategory" @update:modelValue="categoryUpdate" allow-create></model-select>
|
||||
|
||||
<v-label>{{ $t('ShoppingList') }}</v-label>
|
||||
<model-select model="ShoppingList" @update:modelValue="shoppingListUpdate" mode="tags" allow-create></model-select>
|
||||
|
||||
<v-row>
|
||||
<v-col class="pr-0">
|
||||
<v-btn height="80px" color="info" density="compact" size="small" block stacked
|
||||
@@ -76,6 +79,9 @@
|
||||
<v-list-item-subtitle v-if="isDelayed(e)" class="text-info font-weight-bold">
|
||||
{{ $t('PostponedUntil') }} {{ DateTime.fromJSDate(e.delayUntil!).toLocaleString(DateTime.DATETIME_SHORT) }}
|
||||
</v-list-item-subtitle>
|
||||
<v-list-item-subtitle v-if="e.shoppingLists.length > 0" class="text-info font-weight-bold">
|
||||
<v-chip v-for="sl in e.shoppingLists" label size="x-small" variant="outlined" :color="sl.color" :key="sl.id">{{sl.name}}</v-chip>
|
||||
</v-list-item-subtitle>
|
||||
|
||||
<v-btn-group divided border>
|
||||
<v-btn icon="" @click="e.amount = e.amount / 2; updateEntryAmount(e)" v-if="!e.ingredient">
|
||||
@@ -122,8 +128,8 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import {computed} from "vue";
|
||||
import {ApiApi, PatchedShoppingListEntry, ShoppingListEntry, SupermarketCategory} from "@/openapi";
|
||||
import {computed, ref} from "vue";
|
||||
import {ApiApi, PatchedShoppingListEntry, ShoppingList, ShoppingListEntry, SupermarketCategory} from "@/openapi";
|
||||
import ModelSelect from "@/components/inputs/ModelSelect.vue";
|
||||
import {IShoppingListFood} from "@/types/Shopping";
|
||||
import VClosableCardTitle from "@/components/dialogs/VClosableCardTitle.vue";
|
||||
@@ -139,6 +145,8 @@ const {mobile} = useDisplay()
|
||||
const showDialog = defineModel<Boolean>()
|
||||
const shoppingListFood = defineModel<IShoppingListFood>('shoppingListFood', {required: true})
|
||||
|
||||
const shoppingListUpdateLoading = ref(false)
|
||||
|
||||
/**
|
||||
* returns a flat list of entries for the given shopping list food
|
||||
*/
|
||||
@@ -171,6 +179,26 @@ function categoryUpdate(category: SupermarketCategory) {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* change the shopping list for all entries
|
||||
* @param shoppingLists
|
||||
*/
|
||||
function shoppingListUpdate(shoppingLists: ShoppingList[]) {
|
||||
const api = new ApiApi()
|
||||
const promises: Promise<any>[] = []
|
||||
shoppingListUpdateLoading.value = true
|
||||
|
||||
shoppingListFood.value.entries.forEach(e => {
|
||||
e.shoppingLists = shoppingLists
|
||||
promises.push(api.apiShoppingListEntryUpdate({id: e.id, shoppingListEntry: e}).then(r => {
|
||||
|
||||
}).catch(err => {
|
||||
useMessageStore().addError(ErrorMessageType.UPDATE_ERROR, err)
|
||||
}))
|
||||
})
|
||||
Promise.all(promises).finally(() => shoppingListUpdateLoading.value = false)
|
||||
}
|
||||
|
||||
/**
|
||||
* add new entry for currently selected food type
|
||||
*/
|
||||
|
||||
@@ -19,6 +19,12 @@ import {
|
||||
UserFromJSONTyped,
|
||||
UserToJSON,
|
||||
} from './User';
|
||||
import type { ShoppingList } from './ShoppingList';
|
||||
import {
|
||||
ShoppingListFromJSON,
|
||||
ShoppingListFromJSONTyped,
|
||||
ShoppingListToJSON,
|
||||
} from './ShoppingList';
|
||||
import type { ShoppingListRecipe } from './ShoppingListRecipe';
|
||||
import {
|
||||
ShoppingListRecipeFromJSON,
|
||||
@@ -56,6 +62,12 @@ export interface PatchedShoppingListEntry {
|
||||
* @memberof PatchedShoppingListEntry
|
||||
*/
|
||||
listRecipe?: number;
|
||||
/**
|
||||
*
|
||||
* @type {Array<ShoppingList>}
|
||||
* @memberof PatchedShoppingListEntry
|
||||
*/
|
||||
shoppingLists?: Array<ShoppingList>;
|
||||
/**
|
||||
*
|
||||
* @type {Food}
|
||||
@@ -155,6 +167,7 @@ export function PatchedShoppingListEntryFromJSONTyped(json: any, ignoreDiscrimin
|
||||
|
||||
'id': json['id'] == null ? undefined : json['id'],
|
||||
'listRecipe': json['list_recipe'] == null ? undefined : json['list_recipe'],
|
||||
'shoppingLists': json['shopping_lists'] == null ? undefined : ((json['shopping_lists'] as Array<any>).map(ShoppingListFromJSON)),
|
||||
'food': json['food'] == null ? undefined : FoodFromJSON(json['food']),
|
||||
'unit': json['unit'] == null ? undefined : UnitFromJSON(json['unit']),
|
||||
'amount': json['amount'] == null ? undefined : json['amount'],
|
||||
@@ -179,6 +192,7 @@ export function PatchedShoppingListEntryToJSON(value?: Omit<PatchedShoppingListE
|
||||
|
||||
'id': value['id'],
|
||||
'list_recipe': value['listRecipe'],
|
||||
'shopping_lists': value['shoppingLists'] == null ? undefined : ((value['shoppingLists'] as Array<any>).map(ShoppingListToJSON)),
|
||||
'food': FoodToJSON(value['food']),
|
||||
'unit': UnitToJSON(value['unit']),
|
||||
'amount': value['amount'],
|
||||
|
||||
@@ -19,6 +19,12 @@ import {
|
||||
UserFromJSONTyped,
|
||||
UserToJSON,
|
||||
} from './User';
|
||||
import type { ShoppingList } from './ShoppingList';
|
||||
import {
|
||||
ShoppingListFromJSON,
|
||||
ShoppingListFromJSONTyped,
|
||||
ShoppingListToJSON,
|
||||
} from './ShoppingList';
|
||||
import type { ShoppingListRecipe } from './ShoppingListRecipe';
|
||||
import {
|
||||
ShoppingListRecipeFromJSON,
|
||||
@@ -56,6 +62,12 @@ export interface ShoppingListEntry {
|
||||
* @memberof ShoppingListEntry
|
||||
*/
|
||||
listRecipe?: number;
|
||||
/**
|
||||
*
|
||||
* @type {Array<ShoppingList>}
|
||||
* @memberof ShoppingListEntry
|
||||
*/
|
||||
shoppingLists?: Array<ShoppingList>;
|
||||
/**
|
||||
*
|
||||
* @type {Food}
|
||||
@@ -161,6 +173,7 @@ export function ShoppingListEntryFromJSONTyped(json: any, ignoreDiscriminator: b
|
||||
|
||||
'id': json['id'] == null ? undefined : json['id'],
|
||||
'listRecipe': json['list_recipe'] == null ? undefined : json['list_recipe'],
|
||||
'shoppingLists': json['shopping_lists'] == null ? undefined : ((json['shopping_lists'] as Array<any>).map(ShoppingListFromJSON)),
|
||||
'food': FoodFromJSON(json['food']),
|
||||
'unit': json['unit'] == null ? undefined : UnitFromJSON(json['unit']),
|
||||
'amount': json['amount'],
|
||||
@@ -185,6 +198,7 @@ export function ShoppingListEntryToJSON(value?: Omit<ShoppingListEntry, 'listRec
|
||||
|
||||
'id': value['id'],
|
||||
'list_recipe': value['listRecipe'],
|
||||
'shopping_lists': value['shoppingLists'] == null ? undefined : ((value['shoppingLists'] as Array<any>).map(ShoppingListToJSON)),
|
||||
'food': FoodToJSON(value['food']),
|
||||
'unit': UnitToJSON(value['unit']),
|
||||
'amount': value['amount'],
|
||||
|
||||
Reference in New Issue
Block a user