UI update

This commit is contained in:
2026-03-18 19:23:53 +00:00
parent 44333e69e7
commit 1b23c10c78
4 changed files with 250 additions and 235 deletions

View File

@@ -22,7 +22,7 @@
</v-row>
<v-row v-else-if="recipes?.length">
<v-col v-for="recipe in recipes" :key="recipe.id" cols="12" sm="6" md="4">
<v-col v-for="recipe in sortedRecipes" :key="recipe.id" cols="12" sm="6" md="4">
<v-card class="gallery-item-card pa-4">
<v-sheet
height="200"
@@ -81,61 +81,99 @@
</v-card>
<v-dialog v-model="showDetails" max-width="800" persistent>
<v-dialog v-model="showDetails" max-width="950" persistent>
<v-card v-if="selectedRecipe" class="recipe-card pa-8">
<v-btn
v-if="!isEditing"
icon="mdi-close"
variant="text"
position="absolute"
style="top: 10px; right: 10px;"
style="top: 10px; right: 10px; z-index: 10;"
color="#5d4037"
@click="closeDetails"
></v-btn>
<header class="text-center mb-6">
<v-text-field
v-if="isEditing"
v-model="selectedRecipe.title"
variant="underlined"
class="recipe-title-edit"
></v-text-field>
<h2 v-else class="recipe-title">{{ selectedRecipe.title }}</h2>
</header>
<v-row align="start" class="mb-6">
<v-col cols="12" md="4" class="d-flex flex-column align-center">
<v-hover v-slot="{ isHovering, props }">
<v-card
v-bind="props"
width="160"
height="160"
:class="[
'rounded-lg d-flex align-center justify-center cursor-pointer position-relative overflow-hidden',
{ 'image-drop-zone': isEditing }
]"
@click="isEditing ? $refs.fileInput.click() : null"
:elevation="isHovering && isEditing ? 4 : 1"
>
<v-img
v-if="selectedRecipe.imageUrl"
:src="selectedRecipe.imageUrl"
cover
class="rounded-lg fill-height"
></v-img>
<v-divider class="mb-6 separator"></v-divider>
<v-row justify="center" class="px-md-10">
<v-col cols="12" md="5" class="d-flex flex-column align-center">
<div style="width: 100%; max-width: 300px;">
<h3 class="section-header justify-center mb-4">
<v-icon icon="mdi-basket-outline" class="mr-2" size="small"></v-icon>
Ingredients
</h3>
<v-textarea
v-if="isEditing"
v-model="selectedRecipe.ingredients"
variant="outlined"
auto-grow
density="comfortable"
bg-color="rgba(255,255,255,0.3)"
></v-textarea>
<v-list v-else class="ingredients-list">
<v-list-item
v-for="(ing, index) in (Array.isArray(selectedRecipe.ingredients) ? selectedRecipe.ingredients : selectedRecipe.ingredients?.split('\n') || [])"
:key="index"
class="ingredient-item px-0"
<div
v-if="isEditing && (isHovering || !selectedRecipe.imageUrl)"
class="d-flex flex-column align-center justify-center position-absolute"
style="background: rgba(226,215,186,0.4); inset: 0;"
>
{{ ing }}
</v-list-item>
</v-list>
</div>
<v-icon icon="mdi-camera-plus" color="#556b2f" size="large"></v-icon>
<span class="brand-subtitle" style="font-size: 0.7rem; color: #556b2f;">Update Photo</span>
</div>
</v-card>
</v-hover>
<input type="file" ref="fileInput" accept="image/*" style="display: none" @change="handleImageUpload" />
</v-col>
<v-col cols="12" md="8">
<v-text-field
v-if="isEditing"
v-model="selectedRecipe.title"
label="Recipe Title"
variant="underlined"
class="recipe-title-edit"
hide-details
></v-text-field>
<h2 v-else class="recipe-title" style="font-size: 2.5rem;">{{ selectedRecipe.title }}</h2>
</v-col>
</v-row>
<v-divider class="mb-8 separator"></v-divider>
<v-row class="px-md-4">
<v-col cols="12" md="5">
<h3 class="section-header mb-4">
<v-icon icon="mdi-basket-outline" class="mr-2" size="small"></v-icon>
Ingredients
</h3>
<v-textarea
v-if="isEditing"
v-model="selectedRecipe.ingredients"
variant="outlined"
auto-grow
rows="10"
density="comfortable"
class="auth-input recipe-textarea"
bg-color="transparent"
:persistent-placeholder="true"
></v-textarea>
<v-list v-else class="ingredients-list bg-transparent">
<v-list-item
v-for="(ing, index) in (Array.isArray(selectedRecipe.ingredients) ? selectedRecipe.ingredients : selectedRecipe.ingredients?.split('\n') || [])"
:key="index"
class="ingredient-item px-0"
>
{{ ing }}
</v-list-item>
</v-list>
</v-col>
<v-col cols="12" md="7">
<h3 class="section-header justify-center mb-4">
<h3 class="section-header mb-4">
<v-icon icon="mdi-chef-hat" class="mr-2" size="small"></v-icon>
Instructions
</h3>
@@ -145,8 +183,11 @@
v-model="selectedRecipe.instructions"
variant="outlined"
auto-grow
rows="10"
density="comfortable"
bg-color="rgba(255,255,255,0.3)"
class="auth-input recipe-textarea"
bg-color="transparent"
:persistent-placeholder="true"
></v-textarea>
<div v-else
@@ -160,78 +201,18 @@
</v-col>
</v-row>
<v-card-actions v-if="isEditing" class="justify-center mt-6">
<v-btn
variant="text"
@click="saveChanges"
class="save-btn px-8"
>
Save Changes
</v-btn>
<v-btn
variant="text"
@click="isEditing = false"
class="cancel-btn px-8"
>
Cancel
</v-btn>
<v-card-actions v-if="isEditing" class="justify-center mt-8">
<v-btn variant="text" @click="saveChanges" class="save-btn px-8">Save Changes</v-btn>
<v-btn variant="text" @click="isEditing = false" class="cancel-btn px-8">Cancel</v-btn>
</v-card-actions>
<v-divider class="my-6 separator"></v-divider>
<footer class="text-center">
<p class="brand-subtitle" style="font-size: 0.8rem;">
Recorded on {{ new Date(selectedRecipe.createdAt).toLocaleDateString() }}
</p>
</footer>
</v-card>
<v-row justify="center" class="mb-4">
<v-col cols="12" class="d-flex flex-column align-center">
<v-hover v-slot="{ isHovering, props }">
<v-card
v-bind="props"
width="200"
height="200"
class="rounded-lg d-flex align-center justify-center cursor-pointer position-relative"
@click="$refs.fileInput.click()"
:elevation="isHovering ? 4 : 1"
style="border: 2px dashed #d1c7b7; background: #fcfaf5;"
>
<v-img
v-if="selectedRecipe.imageUrl"
:src="selectedRecipe.imageUrl"
cover
class="rounded-lg"
></v-img>
<div
v-if="isEditing && (!selectedRecipe.imageUrl || isHovering)"
class="d-flex flex-column align-center justify-center position-absolute"
style="background: rgba(255,255,255,0.7); inset: 0;"
>
<v-icon icon="mdi-camera-plus" color="#556b2f" size="large"></v-icon>
<span class="brand-subtitle" style="font-size: 0.7rem;">Update Photo</span>
</div>
</v-card>
</v-hover>
<input
type="file"
ref="fileInput"
accept="image/*"
style="display: none"
@change="handleImageUpload"
/>
</v-col>
</v-row>
</v-dialog>
</v-container>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { ref, onMounted, computed } from 'vue'
import '@/assets/css/gallery.css'
const recipes = ref([])
@@ -312,6 +293,7 @@ const saveChanges = async () => {
try {
const payload = {
...selectedRecipe.value,
createdAt: selectedRecipe.value.createdAt || new Date().toISOString(),
ingredients: typeof selectedRecipe.value.ingredients === 'string'
? selectedRecipe.value.ingredients.split('\n').filter(i => i.trim())
: selectedRecipe.value.ingredients,
@@ -334,49 +316,12 @@ const saveChanges = async () => {
closeDetails();
} catch (e) {
console.error("The kitchen ledger could not be updated:", e);
alert("Could not save changes. Please try again.");
}
}
// Mock setup for recipe cards
//const mockData = [
// {
// id: 1,
// title: "Grandma's Secret Bolognese",
// createdAt: new Date().toISOString(),
// ingredients: "2 lbs Ground Beef\n1 Onion, diced\n3 cloves Garlic\n2 cans Crushed Tomatoes\n1 cup Red Wine",
// instructions: "Brown the meat with onions.\nAdd garlic and wine; reduce.\nSimmer with tomatoes for 3 hours."
// },
// {
//id: 2,
//title: "Rustic Sourdough",
//createdAt: new Date().toISOString(),
//ingredients: "500g Flour\n350g Water\n100g Starter\n10g Salt",
//instructions: "Mix and autolyse for 1 hour.\nPerform 4 sets of stretch and folds.\nCold ferment for 12 hours.\nBake at 450°F in a dutch oven."
//}
//]
//recipes.value = mockData
//loading.value = false
//}
//const saveChanges = async () => {
//const index = recipes.value.findIndex(r => r.id === selectedRecipe.value.id)
//if (index !== -1) {
//recipes.value[index] = { ...selectedRecipe.value }
//}
//isEditing.value = false
//showDetails.value = false
//}
const getRecipeIcon = (recipe) => {
if (recipe.icon) return recipe.icon
const t = (recipe.title || '').toLowerCase()
if (t.includes('cake') || t.includes('cookie') || t.includes('dessert')) return 'mdi-cookie'
if (t.includes('soup') || t.includes('stew')) return 'mdi-bowl-mix'
if (t.includes('drink') || t.includes('cocktail')) return 'mdi-glass-cocktail'
return 'mdi-silverware-fork-knife'
}
const sortedRecipes = computed(() => {
return [...recipes.value].sort((a, b) => {
return new Date(b.createdAt) - new Date(a.createdAt)
})
})
</script>

View File

@@ -18,18 +18,19 @@
<v-form @submit.prevent="handleAuth">
<v-expand-transition>
<div v-if="errorMessage"
:class="[
'auth-message',
errorMessage.includes('created') ? 'auth-success' : 'auth-error'
]"
>
<v-icon
:icon="errorMessage.includes('created') ? 'mdi-check-circle-outline' : 'mdi-alert-circle-outline'"
size="small"
class="mr-2"
></v-icon>
{{ errorMessage }}
<div v-if="errorMessage" class="mb-6"> <div
:class="[
'auth-message',
errorMessage.includes('created') ? 'auth-success' : 'auth-error'
]"
>
<v-icon
:icon="errorMessage.includes('created') ? 'mdi-check-circle-outline' : 'mdi-alert-circle-outline'"
size="small"
class="mr-2"
></v-icon>
<span>{{ errorMessage }}</span>
</div>
</div>
</v-expand-transition>
@@ -37,7 +38,7 @@
autofocus
v-model="email"
label="Email Address"
class="mb-4 auth-input"
class="mb-0 auth-input"
color="#8c4a32"
variant="outlined"
prepend-inner-icon="mdi-email-outline"
@@ -52,7 +53,7 @@
v-model="password"
label="Password"
type="password"
class="mb-8 auth-input"
class="mb-6 auth-input"
variant="outlined"
color="#8c4a32"
hide-details
@@ -70,7 +71,7 @@
v-model="confirmPassword"
label="Confirm Password"
type="password"
class="mb-8 auth-input"
class="mb-6 auth-input"
variant="outlined"
color="#8c4a32"
hide-details