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

Upgrade to searchable combobox from native dropdown #feature #18

Merged
merged 1 commit into from
Jul 15, 2022
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
20,728 changes: 10,358 additions & 10,370 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"@babel/plugin-transform-runtime": "^7.17.0",
"@babel/preset-env": "^7.16.11",
"@babel/preset-react": "^7.16.7",
"@headlessui/react": "^1.4.2",
"@headlessui/react": "^1.6.6",
"@inertiajs/inertia": "^0.10.0",
"@inertiajs/inertia-react": "^0.7.0",
"@inertiajs/progress": "^0.2.6",
Expand Down
2 changes: 1 addition & 1 deletion public/css/app.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion public/js/app.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions 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=a851dd183cd50e896983",
"/css/app.css": "/css/app.css?id=58adc592c995babafb87"
"/js/app.js": "/js/app.js?id=e96de6b2d1fad95be392",
"/css/app.css": "/css/app.css?id=98c4b8afbce17b6bb3ca"
}
4 changes: 2 additions & 2 deletions resources/js/Api/brands.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ export const createBrand = ({name, categoryId}) => {
.toPromise();
}

export const updateBrand = ({id, name, category}) => {
export const updateBrand = ({id, name, categoryId}) => {
return client
.mutation(gql`
mutation {
updateBrand(id: ${id} name: "${name}" category_id: ${category}) {
updateBrand(id: ${id} name: "${name}" category_id: ${categoryId}) {
id
name
category {
Expand Down
4 changes: 2 additions & 2 deletions resources/js/Api/transactions.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,11 @@ export const createTransaction = ({amount, brandId, createdAt, note}) => {
.toPromise();
}

export const updateTransaction = ({id, amount, brand, createdAt, note}) => {
export const updateTransaction = ({id, amount, brandId, createdAt, note}) => {
return client
.mutation(gql`
mutation {
updateTransaction(id: ${id} amount: ${amount} brand_id: ${brand} created_at: """${createdAt}""" note: """${note}""") {
updateTransaction(id: ${id} amount: ${amount} brand_id: ${brandId} created_at: """${createdAt}""" note: """${note}""") {
id
amount
created_at
Expand Down
72 changes: 72 additions & 0 deletions resources/js/Components/Global/Combobox.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { useState } from 'react'
import { CheckIcon, SelectorIcon } from '@heroicons/react/solid'
import { Combobox } from '@headlessui/react'

function classNames(...classes) {
return classes.filter(Boolean).join(' ')
}

export default function ComboboxComponent({label, items, initialSelectedItem, onChange, displayInputValue, displayOptionValue}) {
const [query, setQuery] = useState('')
const [selectedItem, setSelectedItem] = useState(initialSelectedItem)

const filteredItems =
query === ''
? items
: items.filter((item) => {
return item.name.toLowerCase().includes(query.toLowerCase())
})

return (
<Combobox as="div" value={selectedItem} onChange={(item) => {
setSelectedItem(item)
onChange(item)
}}>
<Combobox.Label className="block text-sm font-medium text-gray-700">{label}</Combobox.Label>
<div className="relative mt-1">
<Combobox.Input
className="w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-10 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 sm:text-sm"
onChange={(event) => setQuery(event.target.value)}
displayValue={(item) => displayInputValue ? displayInputValue(item) : item?.name}
/>
<Combobox.Button className="absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none">
<SelectorIcon className="h-5 w-5 text-gray-400" aria-hidden="true" />
</Combobox.Button>

{filteredItems.length > 0 && (
<Combobox.Options className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
{filteredItems.map((item) => (
<Combobox.Option
key={item.id}
value={item}
className={({ active }) =>
classNames(
'relative cursor-default select-none py-2 pl-8 pr-4',
active ? 'bg-blue-500 text-white' : 'text-gray-900'
)
}
>
{({ active }) => (
<>
<span className={classNames('block truncate', item.id == selectedItem?.id && 'font-semibold')}>{displayOptionValue ? displayOptionValue(item) : item?.name}</span>

{item.id == selectedItem?.id && (
<span
className={classNames(
'absolute inset-y-0 left-0 flex items-center pl-1.5',
active ? 'text-white' : 'text-blue-500'
)}
>
<CheckIcon className="h-5 w-5" aria-hidden="true" />
</span>
)}
</>
)}
</Combobox.Option>
))}
</Combobox.Options>
)}
</div>
</Combobox>
)
}
19 changes: 7 additions & 12 deletions resources/js/Pages/Brand/Create.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';

import Input from "@/Components/Global/Input";
import Label from "@/Components/Global/Label";
import Combobox from "@/Components/Global/Combobox";
import SidePanel from '@/Components/Global/SidePanel';
import { createBrand } from '../../Api';

Expand Down Expand Up @@ -49,19 +50,13 @@ export default function Edit({categories, showCreate, onClose, onCreate}) {
/>
</div>

<div className="col-span-6 sm:col-span-3 mt-4">
<Label forInput="category" value="Category" />

<select
id="category"
name="category"
value={categoryId}
onChange={(e) => setCategoryId(e.target.value)}
className="mt-1 block w-full py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
>
<option value={0}>Select one</option>
{categories.map(item => <option value={item.id} key={item.id}>{item.name}</option>)}
</select>
<div className="col-span-6 sm:col-span-3 mt-4">
<Combobox
label="Category"
items={categories}
onChange={(item) => setCategoryId(item.id)}
/>
</div>

<div className="flex items-center justify-end mt-4">
Expand Down
33 changes: 14 additions & 19 deletions resources/js/Pages/Brand/Edit.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,38 @@
import React, { useEffect, useState } from "react";

import { updateBrand } from "../../Api";
import Input from "@/Components/Global/Input";
import Label from "@/Components/Global/Label";
import Combobox from "@/Components/Global/Combobox";
import SidePanel from '@/Components/Global/SidePanel';
import { updateBrand } from "../../Api";

export default function Edit({categories, brand, onClose, onUpdate}) {
const [name, setName] = useState(0)
const [category, setCategory] = useState(0)
const [category, setCategory] = useState(null)

useEffect(() => {
if(! brand) return;

setName(brand.name)
if(brand.category) {
setCategory(brand.category.id)
setCategory(brand.category)
}
}, [brand])

const update = () => {
updateBrand({
id: brand.id,
name,
category
categoryId: category.id
})
.then(({data}) => {
onUpdate(data.updateBrand)
setCategory(0)
setCategory(null)
})
.catch(console.error);
}

let isReady = name != '' && category != 0;
let isReady = name != '' && category != null;

return (
<SidePanel toggleOpen={! brand ? false : true}
Expand All @@ -53,19 +54,13 @@ export default function Edit({categories, brand, onClose, onUpdate}) {
</div>

<div className="col-span-6 sm:col-span-3 mt-4">
<label htmlFor="category" className="block text-sm font-medium text-gray-700">
Category
</label>
<select
id="category"
name="category"
value={category}
onChange={(e) => setCategory(e.target.value)}
className="mt-1 block w-full py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
>
<option value={0}>Select one</option>
{categories.map(item => <option value={item.id} key={item.id}>{item.name}</option>)}
</select>
<Combobox
label="Category"
items={categories}
initialSelectedItem={category}
onChange={(item) => setCategory(item)}
displayInputValue={(item) => item?.name ?? ''}
/>
</div>

<div className="flex items-center justify-end mt-4">
Expand Down
34 changes: 14 additions & 20 deletions resources/js/Pages/Transaction/Create.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,37 @@ import React, { useEffect, useState } from "react";
import Input from "@/Components/Global/Input";
import Label from "@/Components/Global/Label";
import { createTransaction } from "../../Api";
import Combobox from "@/Components/Global/Combobox";
import SidePanel from '@/Components/Global/SidePanel';

export default function Create({brands, showCreate, onClose, onCreate}) {
const [amount, setAmount] = useState(0);
const [brandId, setBrandId] = useState(0);
const [brand, setBrand] = useState(null);
const [createdAt, setCreatedAt] = useState('');
const [note, setNote] = useState('');
const [isReady, setIsReady] = useState(false);
const [loading, setLoading] = useState(false);

useEffect(() => {
setIsReady(amount != 0 && brandId != 0 && createdAt != '' ? true : false);
}, [amount, brandId, createdAt])
setIsReady(amount != 0 && brand != null && createdAt != '' ? true : false);
}, [amount, brand, createdAt])

const create = () => {
if(loading || ! isReady) { return; }
setLoading(true);

createTransaction({
amount,
brandId,
brandId: brand.id,
createdAt,
note
})
.then(({data}) => {
onCreate(data.createTransaction)
setBrandId(0)
setBrand(null)
setAmount(0)
setCreatedAt('')
setNote('')
setLoading(false);
})
.catch(console.error);
Expand Down Expand Up @@ -67,21 +69,13 @@ export default function Create({brands, showCreate, onClose, onCreate}) {
</div>

<div className="col-span-6 sm:col-span-3 mt-4">
<Label forInput="brand" value="Brand" />

<select
id="brand"
name="brand"
value={brandId}
onChange={(e) => setBrandId(e.target.value)}
className="mt-1 block w-full py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
>
<option value={0}>Select one</option>
{brands.map(brand => <option value={brand.id} key={brand.id}>
{brand.name}
{brand.category ? " ("+brand.category.name+")" : ''}
</option>)}
</select>
<Combobox
label="Brand"
items={brands}
onChange={(item) => setBrand(item)}
displayInputValue={(item) => item ? `${item.name} (${item.category?.name ?? 'N/A'})` : ''}
displayOptionValue={(item) => item ? `${item.name} (${item.category?.name ?? 'N/A'})` : ''}
/>
</div>

<div className="mt-4">
Expand Down
32 changes: 13 additions & 19 deletions resources/js/Pages/Transaction/Edit.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
import React, { useEffect, useState } from "react";

import { updateTransaction } from "../../Api";
import Input from "@/Components/Global/Input";
import Label from "@/Components/Global/Label";
import Combobox from "@/Components/Global/Combobox";
import SidePanel from '@/Components/Global/SidePanel';
import { updateTransaction } from "../../Api";

export default function Edit({brands, transaction, onClose, onUpdate}) {
const [amount, setAmount] = useState(0)
const [createdAt, setCreatedAt] = useState('')
const [brand, setBrand] = useState(0)
const [brand, setBrand] = useState(null)
const [note, setNote] = useState('')

useEffect(() => {
if(! transaction) return;

setAmount(transaction.amount)
setBrand(transaction.brand.id)
setBrand(transaction.brand)
setCreatedAt(transaction.created_at)
setNote(transaction.note ?? '')
}, [transaction])
Expand All @@ -24,7 +25,7 @@ export default function Edit({brands, transaction, onClose, onUpdate}) {
updateTransaction({
id: transaction.id,
amount,
brand,
brandId: brand.id,
createdAt,
note
})
Expand Down Expand Up @@ -66,21 +67,14 @@ export default function Edit({brands, transaction, onClose, onUpdate}) {
</div>

<div className="col-span-6 sm:col-span-3 mt-4">
<label htmlFor="brand" className="block text-sm font-medium text-gray-700">
Brand
</label>
<select
id="brand"
name="brand"
value={brand}
onChange={(e) => setBrand(e.target.value)}
className="mt-1 block w-full py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
>
{brands.map(brand => <option value={brand.id} key={brand.id}>
{brand.name}
{brand.category ? " ("+brand.category.name+")" : ''}
</option>)}
</select>
<Combobox
label="Brand"
items={brands}
initialSelectedItem={brand}
onChange={(item) => setBrand(item)}
displayInputValue={(item) => item ? `${item.name} (${item.category?.name ?? 'N/A'})` : ''}
displayOptionValue={(item) => item ? `${item.name} (${item.category?.name ?? 'N/A'})` : ''}
/>
</div>

<div className="mt-4">
Expand Down