Add search and filtering to food index

This commit is contained in:
Christopher C. Wells 2021-01-23 16:09:17 -08:00
parent 2e55ea1d98
commit ae626e5392
5 changed files with 111 additions and 42 deletions

View File

@ -38,7 +38,14 @@ class FoodAdapter extends AbstractAdapter
*/ */
protected function filter($query, Collection $filters) protected function filter($query, Collection $filters)
{ {
$this->filterWithScopes($query, $filters); if ($term = $filters->get('search')) {
$query->where('foods.name', 'like', "%{$term}%")
->orWhere('foods.detail', 'like', "%{$term}%")
->orWhere('foods.brand', 'like', "%{$term}%");
}
else {
$this->filterWithScopes($query, $filters);
}
} }
} }

View File

@ -30,7 +30,7 @@ class FoodSchema extends SchemaProvider
public function getAttributes($resource) public function getAttributes($resource)
{ {
return [ return [
'name' => $resource->created_at, 'name' => $resource->name,
'detail' => $resource->detail, 'detail' => $resource->detail,
'brand' => $resource->brand, 'brand' => $resource->brand,
'calories' => $resource->calories, 'calories' => $resource->calories,

View File

@ -49,7 +49,6 @@
</div> </div>
</div> </div>
@once @once
@push('scripts') @push('scripts')
<script type="text/javascript"> <script type="text/javascript">

View File

@ -1,3 +1,5 @@
<button {{ $attributes->merge(['type' => 'submit', 'class' => 'inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 active:bg-gray-900 focus:outline-none focus:border-gray-900 focus:ring ring-gray-300 disabled:opacity-25 transition ease-in-out duration-150']) }}> @props(['color' => 'gray', 'textColor' => 'white'])
<button {{ $attributes->merge(['type' => 'submit', 'class' => "inline-flex items-center px-4 py-2 bg-{$color}-800 border border-transparent rounded-md font-semibold text-xs text-{$textColor} uppercase tracking-widest hover:bg-{$color}-700 active:bg-{$color}-900 focus:outline-none focus:border-{$color}-900 focus:ring ring-{$color}-300 disabled:opacity-25 transition ease-in-out duration-150"]) }}>
{{ $slot }} {{ $slot }}
</button> </button>

View File

@ -9,49 +9,110 @@
<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="grid grid-cols-3 gap-4"> <div x-data="foods()" x-init="loadMore()">
@foreach ($foods as $food) <x-inputs.input type="text"
<div class="p-2 font-light rounded-t border-2 border-gray-400"> name="search"
<a class="h-6 w-6 text-gray-500 hover:text-gray-700 hover:border-gray-300 float-right text-sm" placeholder="Search..."
href="{{ route('foods.edit', $food) }}"> autocomplete="off"
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"> class="w-full mb-4"
<path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z" /> @input.debounce.400ms="search($event)" />
<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" /> <div class="grid grid-cols-3 gap-4">
</svg> <template x-for="food in foods" :key="food">
</a> <div class="p-2 font-light rounded-t border-2 border-gray-400">
<div class="text-2xl"> <a class="h-6 w-6 text-gray-500 hover:text-gray-700 hover:border-gray-300 float-right text-sm"
{{ $food->name }}@if($food->detail), <span class="text-gray-500">{{ $food->detail }}</span>@endif href="/TODO">
</div> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
@if($food->brand) <path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z" />
<div class="text-xl text-gray-600"> <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" />
{{ $food->brand }} </svg>
</a>
<div class="text-2xl">
<span x-text="food.name"></span><span class="text-gray-500" x-text="`, ${food.detail}`" x-show="food.detail"></span>
</div>
<div class="text-xl text-gray-600" x-text="food.brand" x-show="food.brand"></div>
<div class="font-bold">
Serving size <span x-text="food.servingSizeFormatted"></span>
<span x-text="food.servingUnit"></span>
<span x-text="`(${food.servingWeight}g)`"></span>
</div>
<div class="grid grid-cols-2 text-sm border-t-8 border-black pt-2">
<div class="col-span-2 text-xs font-bold text-right">Amount per serving</div>
<div class="font-extrabold text-lg border-b-4 border-black">Calories</div>
<div class="font-extrabold text-right text-lg border-b-4 border-black" x-text="`${food.calories}g`"></div>
<div class="font-bold border-b border-gray-300">Fat</div>
<div class="text-right border-b border-gray-300" x-text="`${food.fat}g`"></div>
<div class="font-bold border-b border-gray-300">Cholesterol</div>
<div class="text-right border-b border-gray-300" x-text="`${Math.round(food.cholesterol*1000)}mg`"></div>
<div class="font-bold border-b border-gray-300">Sodium</div>
<div class="text-right border-b border-gray-300" x-text="`${Math.round(food.sodium*1000)}mg`">{</div>
<div class="font-bold border-b border-gray-300">Carbohydrates</div>
<div class="text-right border-b border-gray-300" x-text="`${food.carbohydrates}g`"></div>
<div class="font-bold">Protein</div>
<div class="text-right" x-text="`${food.protein}g`"></div>
</div> </div>
@endif
<div class="font-bold">
Serving size {{ \App\Support\Number::fractionStringFromFloat($food->serving_size) }}
{{ $food->serving_unit }}
({{ $food->serving_weight }}g)
</div> </div>
<div class="grid grid-cols-2 text-sm border-t-8 border-black pt-2"> </template>
<div class="col-span-2 text-xs font-bold text-right">Amount per serving</div> </div>
<div class="font-extrabold text-lg border-b-4 border-black">Calories</div> <x-inputs.button
<div class="font-extrabold text-right text-lg border-b-4 border-black">{{$food->calories}}</div> class="text-xl mt-4"
<div class="font-bold border-b border-gray-300">Fat</div> color="blue"
<div class="text-right border-b border-gray-300">{{ $food->fat < 1 ? $food->fat * 1000 . "m" : $food->fat }}g</div> type="button"
<div class="font-bold border-b border-gray-300">Cholesterol</div> x-show="morePages"
<div class="text-right border-b border-gray-300">{{ $food->cholesterol < 1 ? $food->cholesterol * 1000 . "m" : $food->cholesterol }}g</div> @click.prevent="loadMore()">
<div class="font-bold border-b border-gray-300">Sodium</div> Load more
<div class="text-right border-b border-gray-300">{{ $food->sodium < 1 ? $food->sodium * 1000 . "m" : $food->sodium }}g</div> </x-inputs.button>
<div class="font-bold border-b border-gray-300">Carbohydrates</div>
<div class="text-right border-b border-gray-300">{{ $food->carbohydrates < 1 ? $food->carbohydrates * 1000 . "m" : $food->carbohydrates }}g</div>
<div class="font-bold">Protein</div>
<div class="text-right">{{ $food->protein < 1 ? $food->protein * 1000 . "m" : $food->protein }}g</div>
</div>
</div>
@endforeach
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
@once
@push('scripts')
<script type="text/javascript">
let foods = () => {
return {
foods: [],
number: 1,
size: 12,
morePages: true,
searchTerm: null,
reset() {
this.foods = [];
this.number = 1;
this.searchTerm = null;
this.morePages = true;
},
loadMore() {
let url = `{{ route('api:v1:foods.index') }}?page[number]=${this.number}&page[size]=${this.size}`;
if (this.searchTerm) {
url += `&filter[search]=${this.searchTerm}`;
}
console.log(url);
fetch(url)
.then(response => response.json())
.then(data => {
this.foods = [...this.foods, ...data.data.map(food => food.attributes)];
if (this.number >= data.meta.page['last-page']) {
this.morePages = false;
} else {
this.number++;
}
});
},
search(e) {
this.reset();
if (e.target.value !== '') {
this.searchTerm = e.target.value;
this.loadMore();
}
else {
this.loadMore();
}
}
}
}
</script>
@endpush
@endonce
</x-app-layout> </x-app-layout>