Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add brand search functionality #feature #58

Merged
merged 1 commit into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions app/Models/Brand.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

namespace App\Models;

use App\Contracts\Searchable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class Brand extends Model
class Brand extends Model implements Searchable
{
use HasFactory;

protected $guarded = [];

protected static function booted()
Expand Down Expand Up @@ -39,4 +41,17 @@ public static function findOrCreateNew($name)

return static::create(['name' => $name]);
}

/**
* @param $query
* @return Builder
*/
public static function search($query): Builder
{
return (new static())->newQuery()
->where('name', 'LIKE', "%$query%")
->orWhereHas('category', function($builder) use($query) {
return $builder->where('name', 'LIKE', "%$query%");
});
}
}
64 changes: 64 additions & 0 deletions config/graphiql.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php declare(strict_types=1);

return [
/*
|--------------------------------------------------------------------------
| Routes configuration
|--------------------------------------------------------------------------
|
| Set the key as URI at which the GraphiQL UI can be viewed,
| and add any additional configuration for the route.
|
| You can add multiple routes pointing to different GraphQL endpoints.
|
*/

'routes' => [
'/graphiql' => [
'name' => 'graphiql',
// 'middleware' => ['web']
// 'prefix' => '',
// 'domain' => 'graphql.' . env('APP_DOMAIN', 'localhost'),

/*
|--------------------------------------------------------------------------
| Default GraphQL endpoint
|--------------------------------------------------------------------------
|
| The default endpoint that the GraphiQL UI is set to.
| It assumes you are running GraphQL on the same domain
| as GraphiQL, but can be set to any URL.
|
*/

'endpoint' => '/graphql',

/*
|--------------------------------------------------------------------------
| Subscription endpoint
|--------------------------------------------------------------------------
|
| The default subscription endpoint the GraphiQL UI uses to connect to.
| Tries to connect to the `endpoint` value if `null` as ws://{{endpoint}}
|
| Example: `ws://your-endpoint` or `wss://your-endpoint`
|
*/

'subscription-endpoint' => env('GRAPHIQL_SUBSCRIPTION_ENDPOINT', null),
],
],

/*
|--------------------------------------------------------------------------
| Control GraphiQL availability
|--------------------------------------------------------------------------
|
| Control if the GraphiQL UI is accessible at all.
| This allows you to disable it in certain environments,
| for example you might not want it active in production.
|
*/

'enabled' => env('GRAPHIQL_ENABLED', false),
];
2 changes: 1 addition & 1 deletion graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ type Query {
@orderBy(column: id direction: DESC)

allBrands: [Brand!]! @all
brands: [Brand!]!
brands(search: String @search): [Brand!]!
@paginate(defaultCount: 50)
@lazyLoad(relations: ["category"])
@orderBy(column: id direction: DESC)
Expand Down
2 changes: 1 addition & 1 deletion public/js/app.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion public/mix-manifest.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"/js/app.js": "/js/app.js?id=86df5ca868b7e285c1c332bbe2d96034",
"/js/app.js": "/js/app.js?id=e46970d4146019f17f019679336faf94",
"/css/app.css": "/css/app.css?id=a29af113c48845a119a56553951013db"
}
4 changes: 2 additions & 2 deletions resources/js/Api/brands.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ export const getAllBrands = () => {
.toPromise();
}

export const getBrands = (page) => {
export const getBrands = (page, searchQuery) => {
return client
.query(gql`
query {
brands(page: ${page}) {
brands(search: """${searchQuery}""" page: ${page}) {
data {
id
name
Expand Down
61 changes: 49 additions & 12 deletions resources/js/Pages/Brand/Index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, {useEffect, useMemo, useState} from 'react';
import { PencilAltIcon, TrashIcon } from '@heroicons/react/outline';
import { Head } from '@inertiajs/inertia-react';

Expand All @@ -10,12 +10,14 @@ import Button from '@/Components/Global/Button';
import Delete from '@/Components/Domain/Delete';
import { getAllCategories, getBrands } from '@/Api';
import { animateRowItem } from '@/Utils';
import {debounce} from "lodash";

export default function Index({auth}) {
const [brands, setBrands] = useState([]);
const [allCategories, setAllCategories] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [hasMorePages, setHasMorePages] = useState(true);
const [searchQuery, setSearchQuery] = useState('');
const [loading, setLoading] = useState(false);
const [editItem, setEditItem] = useState(null);
const [showCreate, setShowCreate] = useState(false);
Expand All @@ -33,7 +35,7 @@ export default function Index({auth}) {
if(! hasMorePages) return;
setLoading(true);

getBrands(currentPage)
getBrands(currentPage, searchQuery)
.then(({data}) => {
setBrands([...brands, ...data.brands.data])
setHasMorePages(data.brands.paginatorInfo.hasMorePages)
Expand All @@ -42,6 +44,18 @@ export default function Index({auth}) {
.catch(console.error);
}, [currentPage]);

useEffect(() => {
setLoading(true);

getBrands(currentPage, searchQuery)
.then(({data}) => {
setBrands([...brands, ...data.brands.data])
setHasMorePages(data.brands.paginatorInfo.hasMorePages)
setLoading(false);
})
.catch(console.error);
}, [searchQuery]);

const onCreate = (createdItem) => {
setShowCreate(false)
setBrands([createdItem, ...brands])
Expand Down Expand Up @@ -69,17 +83,38 @@ export default function Index({auth}) {
})
}

return (
<Authenticated auth={auth}
header={
<div className='flex justify-between items-center'>
<h2 className="font-semibold text-xl text-gray-800 leading-tight">
Brands
</h2>

<Button children={"Create Brand"} type="button" onClick={() => setShowCreate(true)} />
const performSearchHandler = (e) => {
setBrands([]);
setSearchQuery(e.target.value ?? '');
setCurrentPage(1);
}

const performSearch = useMemo(
() => debounce(performSearchHandler, 300)
, []);

const header = <div className="w-full pb-3 mb-4 px-4 sm:px-0">
<h2 className='text-lg text-gray-600'>Brands</h2>

<div className='flex justify-between items-center mt-2'>
<div>
<div className="relative flex items-center">
<input
type="text"
name="search"
placeholder='🔍 Search'
onChange={performSearch}
className="block w-full rounded-full border-0 py-1.5 pr-14 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-500 sm:text-sm sm:leading-6"
/>
</div>
}>
</div>

<Button children={"Create Brand"} type="button" onClick={() => setShowCreate(true)} />
</div>
</div>

return (
<Authenticated auth={auth}>
<Head title="Brands" />

<Create showCreate={showCreate}
Expand All @@ -103,6 +138,8 @@ export default function Index({auth}) {

<div className="py-12">
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
{header}

<div className="flex flex-col">
{brands.length > 0 && <div className="-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
<div className="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8">
Expand Down
41 changes: 21 additions & 20 deletions resources/js/Pages/Transaction/Index.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,32 @@ export default function Index({auth}) {
setTransactions([]);
setSearchQuery(e.target.value ?? '');
setCurrentPage(1);
setHasMorePages(true);
}

const performSearch = useMemo(
() => debounce(performSearchHandler, 300)
, []);

const header = <div className="w-full pb-3 mb-4 px-4 sm:px-0">
<h2 className='text-lg text-gray-600'>Transactions</h2>

<div className='flex justify-between items-center mt-2'>
<div>
<div className="relative flex items-center">
<input
type="text"
name="search"
placeholder='🔍 Search'
onChange={performSearch}
className="block w-full rounded-full border-0 py-1.5 pr-14 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-500 sm:text-sm sm:leading-6"
/>
</div>
</div>

<Button children={"Create Transaction"} type="button" onClick={() => setShowCreate(true)} />
</div>
</div>

return (
<Authenticated auth={auth}>
<Head title="Transactions" />
Expand All @@ -117,25 +136,7 @@ export default function Index({auth}) {

<div className="py-12">
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div className="w-full pb-3 mb-4 px-4 sm:px-0">
<h2 className='text-lg text-gray-600'>Transactions</h2>

<div className='flex justify-between items-center mt-2'>
<div>
<div className="relative flex items-center">
<input
type="text"
name="search"
placeholder='🔍 Search'
onChange={performSearch}
className="block w-full rounded-full border-0 py-1.5 pr-14 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-500 sm:text-sm sm:leading-6"
/>
</div>
</div>

<Button children={"Create Transaction"} type="button" onClick={() => setShowCreate(true)} />
</div>
</div>
{header}

<div className="flex flex-col">
{transactions.length > 0 && <div className="-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
Expand Down
13 changes: 13 additions & 0 deletions tests/Unit/Models/Brands/BrandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,17 @@ public function it_has_transactions()

$this->assertCount(1, $sut->transactions);
}

/** @test */
public function is_does_search_about_amount_brand_or_note()
{
Brand::factory()->forCategory(['name' => 'internet'])->create(['name' => 'google']);
Brand::factory()->forCategory(['name' => 'shopping'])->create(['name' => 'ikea']);
Brand::factory()->forCategory(['name' => 'shopping'])->create(['name' => 'lulu']);

$this->assertCount(1, Brand::search('goo')->get());
$this->assertCount(1, Brand::search('internet')->get());
$this->assertCount(2, Brand::search('shopping')->get());
$this->assertCount(0, Brand::search('other')->get());
}
}