Fix edit link in Foods list

This commit is contained in:
Christopher C. Wells 2021-01-23 20:14:17 -08:00
parent c29c88776f
commit 2a6549c6d2
11 changed files with 155 additions and 26 deletions

View File

@ -19,8 +19,7 @@ class FoodController extends Controller
*/ */
public function index(): View public function index(): View
{ {
return view('foods.index') return view('foods.index');
->with('foods', Food::all()->sortBy('name'));
} }
/** /**
@ -86,8 +85,13 @@ class FoodController extends Controller
$attributes['serving_size'] = Number::floatFromString($attributes['serving_size']); $attributes['serving_size'] = Number::floatFromString($attributes['serving_size']);
$attributes['name'] = Str::lower($attributes['name']); $attributes['name'] = Str::lower($attributes['name']);
$food->fill(array_filter($attributes))->save(); $food->fill(array_filter($attributes))->save();
return redirect(route('foods.show', $food))
->with('message', 'Changes saved!'); // Sync tags.
$tags = explode(',', $request->get('tags'));
$food->syncTags($tags);
session()->flash('message', "Food {$food->name} updated!");
return redirect()->route('foods.show', $food);
} }
/** /**

View File

@ -186,6 +186,10 @@ class RecipeController extends Controller
'servings' => (int) $input['servings'], 'servings' => (int) $input['servings'],
]); ]);
// Sync tags.
$tags = explode(',', $request->get('tags'));
$recipe->syncTags($tags);
try { try {
DB::transaction(function () use ($recipe, $input) { DB::transaction(function () use ($recipe, $input) {
if (!$recipe->save()) { if (!$recipe->save()) {

View File

@ -38,7 +38,12 @@ class TagAdapter extends AbstractAdapter
*/ */
protected function filter($query, Collection $filters) protected function filter($query, Collection $filters)
{ {
$this->filterWithScopes($query, $filters); if ($term = $filters->get('name')) {
$query->where('tags.name', 'like', "%{$term}%");
}
else {
$this->filterWithScopes($query, $filters);
}
} }
} }

View File

@ -26,6 +26,7 @@ class FoodSchema extends SchemaProvider
public function getAttributes($resource): array public function getAttributes($resource): array
{ {
return [ return [
'slug' => $resource->slug,
'name' => $resource->name, 'name' => $resource->name,
'detail' => $resource->detail, 'detail' => $resource->detail,
'brand' => $resource->brand, 'brand' => $resource->brand,
@ -41,6 +42,8 @@ class FoodSchema extends SchemaProvider
'servingWeight' => $resource->serving_weight, 'servingWeight' => $resource->serving_weight,
'createdAt' => $resource->created_at, 'createdAt' => $resource->created_at,
'updatedAt' => $resource->updated_at, 'updatedAt' => $resource->updated_at,
'showUrl' => route('foods.show', $resource),
'editUrl' => route('foods.edit', $resource),
]; ];
} }

View File

@ -26,6 +26,7 @@ class RecipeSchema extends SchemaProvider
public function getAttributes($resource): array public function getAttributes($resource): array
{ {
return [ return [
'slug' => $resource->slug,
'name' => $resource->name, 'name' => $resource->name,
'description' => $resource->description, 'description' => $resource->description,
'source' => $resource->source, 'source' => $resource->source,
@ -44,6 +45,8 @@ class RecipeSchema extends SchemaProvider
'sodiumTotal' => $resource->sodiumTotal(), 'sodiumTotal' => $resource->sodiumTotal(),
'createdAt' => $resource->created_at, 'createdAt' => $resource->created_at,
'updatedAt' => $resource->updated_at, 'updatedAt' => $resource->updated_at,
'showUrl' => route('recipes.show', $resource),
'editUrl' => route('recipes.edit', $resource),
]; ];
} }

View File

@ -0,0 +1,98 @@
<div x-data data-tags="{{ $defaultTags ?? [] }}">
<div x-data="tagSelect()" x-init="init('parentEl')" @click.away="clearSearch()" @keydown.escape="clearSearch()">
<div class="relative" @keydown.enter.prevent="addTag(searchTerm)">
<x-inputs.input type="hidden"
name="tags"
value=""
x-model="tags"/>
<x-inputs.label for="tag_picker" value="Tags"/>
<div class="flex flex-row items-center">
<x-inputs.input
type="text"
name="tag_picker"
class="mr-2"
x-model="searchTerm"
x-ref="searchTerm"
@input="search($event.target.value)"
placeholder="Enter some tags..." />
<div x-show="open">
<div class="absolute z-40 left-0 mt-2">
<div class="py-1 text-sm bg-white rounded shadow-lg border border-gray-300">
<template x-for="result in results">
<a @click.prevent="addTag(result)"
x-text="result"
class="block py-1 px-5 cursor-pointer hover:bg-indigo-600 hover:text-white"></a>
</template>
<a @click.prevent="addTag(searchTerm)"
class="block py-1 px-5 cursor-pointer hover:bg-indigo-600 hover:text-white"
x-text="searchTerm"></a>
</div>
</div>
</div>
<template x-for="(tag, index) in tags">
<div class="bg-indigo-100 inline-flex items-center text-sm rounded mr-1">
<span class="ml-2 mr-1 leading-relaxed truncate max-w-xs" x-text="tag"></span>
<button @click.prevent="removeTag(index)" class="w-6 h-8 inline-block align-middle text-gray-500 hover:text-gray-600 focus:outline-none">
<svg class="w-6 h-6 fill-current mx-auto" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M15.78 14.36a1 1 0 0 1-1.42 1.42l-2.82-2.83-2.83 2.83a1 1 0 1 1-1.42-1.42l2.83-2.82L7.3 8.7a1 1 0 0 1 1.42-1.42l2.83 2.83 2.82-2.83a1 1 0 0 1 1.42 1.42l-2.83 2.83 2.83 2.82z"/></svg>
</button>
</div>
</template>
</div>
</div>
</div>
</div>
@once
@push('scripts')
<script type="text/javascript">
function tagSelect() {
return {
open: false,
searchTerm: '',
tags: [],
results: [],
init() {
this.tags = JSON.parse(this.$el.parentNode.getAttribute('data-tags'));
},
addTag(tag) {
tag = tag.trim()
if (tag !== "" && !this.hasTag(tag)) {
this.tags.push( tag )
}
this.clearSearch()
this.$refs.searchTerm.focus()
},
hasTag(tag) {
let foundTag = this.tags.find(e => {
return e.toLowerCase() === tag.toLowerCase()
})
return foundTag !== undefined
},
removeTag(index) {
this.tags.splice(index, 1)
},
search(q) {
if ( q.includes(",") ) {
q.split(",").forEach((val) => {
this.addTag(val)
}, this)
}
fetch('{{ route('api:v1:tags.index') }}?filter[name]=' + q)
.then(response => response.json())
.then(data => {
this.results = data.data.map(tag => tag.attributes.name);
});
this.toggleSearch();
},
clearSearch() {
this.searchTerm = ''
this.toggleSearch();
},
toggleSearch() {
this.open = this.searchTerm !== '';
}
}
}
</script>
@endpush
@endonce

View File

@ -110,7 +110,12 @@
</div> </div>
@endforeach @endforeach
</div> </div>
<!-- Tags -->
<x-tagger :defaultTags="$food->tags->pluck('name')"/>
</div> </div>
<div class="flex items-center justify-end mt-4"> <div class="flex items-center justify-end mt-4">
<x-inputs.button class="ml-3"> <x-inputs.button class="ml-3">
{{ ($food->exists ? 'Save' : 'Add') }} {{ ($food->exists ? 'Save' : 'Add') }}

View File

@ -20,7 +20,7 @@
<template x-for="food in foods" :key="food"> <template x-for="food in foods" :key="food">
<div class="p-2 font-light rounded-t border-2 border-gray-400"> <div class="p-2 font-light rounded-t border-2 border-gray-400">
<a class="h-6 w-6 text-gray-500 hover:text-gray-700 hover:border-gray-300 float-right text-sm" <a class="h-6 w-6 text-gray-500 hover:text-gray-700 hover:border-gray-300 float-right text-sm"
href="/TODO"> x-bind:href="food.editUrl">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z" /> <path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z" />
<path fill-rule="evenodd" d="M2 6a2 2 0 012-2h4a1 1 0 010 2H4v10h10v-4a1 1 0 112 0v4a2 2 0 01-2 2H4a2 2 0 01-2-2V6z" clip-rule="evenodd" /> <path fill-rule="evenodd" d="M2 6a2 2 0 012-2h4a1 1 0 010 2H4v10h10v-4a1 1 0 112 0v4a2 2 0 01-2 2H4a2 2 0 01-2-2V6z" clip-rule="evenodd" />
@ -76,7 +76,7 @@
number: 1, number: 1,
size: 12, size: 12,
morePages: true, morePages: true,
searchTerm: null, searchTerm: '{{ $defaultSearch ?? null }}',
reset() { reset() {
this.foods = []; this.foods = [];
this.number = 1; this.number = 1;
@ -88,7 +88,6 @@
if (this.searchTerm) { if (this.searchTerm) {
url += `&filter[search]=${this.searchTerm}`; url += `&filter[search]=${this.searchTerm}`;
} }
console.log(url);
fetch(url) fetch(url)
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {

View File

@ -1,4 +1 @@
@php @include('foods.index', ['defaultSearch' => $food->name])
$foods = [$food];
@endphp
@include('foods.index')

View File

@ -39,16 +39,6 @@
</div> </div>
</div> </div>
<!-- Description -->
<div>
<x-inputs.label for="description" :value="__('Description')" />
<x-inputs.textarea id="description"
class="block mt-1 w-full"
name="description"
:value="old('description', $recipe->description)" />
</div>
<!-- Source --> <!-- Source -->
<div> <div>
<x-inputs.label for="source" :value="__('Source')" /> <x-inputs.label for="source" :value="__('Source')" />
@ -59,6 +49,19 @@
name="source" name="source"
:value="old('source', $recipe->source)" /> :value="old('source', $recipe->source)" />
</div> </div>
<!-- Description -->
<div>
<x-inputs.label for="description" :value="__('Description')" />
<x-inputs.textarea id="description"
class="block mt-1 w-full"
name="description"
:value="old('description', $recipe->description)" />
</div>
<!-- Tags -->
<x-tagger :defaultTags="$recipe->tags->pluck('name')"/>
</div> </div>
<!-- Ingredients --> <!-- Ingredients -->

View File

@ -15,10 +15,18 @@
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg"> <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200"> <div class="p-6 bg-white border-b border-gray-200">
<div class="mb-2 text-gray-500 text-sm"> @if($recipe->tags)
<span class="font-extrabold">Source:</span> <div class="mb-2 text-gray-700 text-sm">
{{ $recipe->source }} <span class="font-extrabold">Tags:</span>
</div> {{ implode(', ', $recipe->tags->pluck('name')->all()) }}
</div>
@endif
@if($recipe->source)
<div class="mb-2 text-gray-500 text-sm">
<span class="font-extrabold">Source:</span>
{{ $recipe->source }}
</div>
@endif
<h3 class="mb-2 font-bold">Description</h3> <h3 class="mb-2 font-bold">Description</h3>
<div class="mb-2 text-gray-800">{{ $recipe->description }}</div> <div class="mb-2 text-gray-800">{{ $recipe->description }}</div>
<h3 class="mb-2 font-bold">Ingredients</h3> <h3 class="mb-2 font-bold">Ingredients</h3>