Skip to content

Commit

Permalink
added searchable select component, added new items page, removed old
Browse files Browse the repository at this point in the history
items page
  • Loading branch information
towelie committed Dec 17, 2024
1 parent e39151a commit ec1745c
Show file tree
Hide file tree
Showing 13 changed files with 550 additions and 48 deletions.
69 changes: 60 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"dependencies": {
"@radix-ui/react-avatar": "^1.1.1",
"@radix-ui/react-checkbox": "^1.1.2",
"@radix-ui/react-dialog": "^1.1.3",
"@radix-ui/react-dialog": "^1.1.4",
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-popover": "^1.1.4",
Expand All @@ -36,6 +36,7 @@
"@supabase/supabase-js": "^2.47.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.0.4",
"jsonwebtoken": "^9.0.2",
"lucide-react": "^0.468.0",
"motion": "^11.13.5",
Expand Down
7 changes: 4 additions & 3 deletions src/actions/items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import { DBResult, Item, RevalidationPaths } from "@/lib/supabase/complex_types"
import { createClient } from "@/lib/supabase/server";
import { revalidateAll } from "@/lib/utils";

export async function createItem(itemData: Omit<Item, 'id' | 'created_at'>, revalidatePaths: RevalidationPaths): DBResult<Item> {
export async function createItem(itemData: Omit<Item, 'id' | 'created_at' | 'last_bought'>, revalidatePaths: RevalidationPaths): DBResult<Item> {
const supabase = await createClient()

const result = await supabase
.from('shopping_items')
.from('item')
.insert(itemData)
.single();

Expand All @@ -19,7 +19,8 @@ export async function createItem(itemData: Omit<Item, 'id' | 'created_at'>, reva

export async function getItems(): DBResult<Item[]> {
return (await createClient())
.from('shopping_items')
.from('item')
.select(`*`)
.order('name', { ascending: true })
}

57 changes: 25 additions & 32 deletions src/app/(app)/items/page.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,34 @@
import { getItems } from "@/actions/items";
import AddItemForm from "@/components/forms/AddItemForm";
import { Card, CardContent } from "@/components/ui/card";
import { Table, TableBody, TableCell, TableRow } from "@/components/ui/table";
import { Item } from "@/lib/supabase/complex_types";
import { notFound } from "next/navigation";
import { getItems } from "@/actions/items"
import DebugState from "@/components/DebugState"
import AddItemForm from "@/components/forms/AddItemForm"
import ItemList from "@/components/ItemList"
import { SkeletonItemsList } from "@/components/skeltons/SkeletonItemsList"
import { Suspense } from "react"

export default async function ItemsPage() {
const { data: items, error } = await getItems()
return (
<div className="container mx-auto py-10 space-y-5">
<AddItemForm />
<div>
<h1 className="pt-10 pb-5 pl-2 text-xl font-sans font-semibold">All Items</h1>
<Suspense fallback={<SkeletonItemsList />}>
<ItemsWrapper />
</Suspense>
</div>
</div>
)
}

if (error) {
throw error
}
async function ItemsWrapper() {
const items = await getItems()

if (!items) {
notFound()
}
console.log("items", items)

return (
<div className="space-y-3">
<h1>items</h1>
<AddItemForm />

<Card>
<CardContent>
<Table>
<TableBody>
{items.map((item: Item) => (
<TableRow key={item.id}>
<TableCell>{item.name}</TableCell>
<TableCell>{item.type}</TableCell>
<TableCell>{item.order}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</CardContent>
</Card>
<div className="space-y-5">
<DebugState state={items} title="Items Page" />
<ItemList items={items.data || []} />
</div>
)
}

27 changes: 27 additions & 0 deletions src/components/ItemList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"use client"

import { Item } from "@/lib/supabase/complex_types"
import { Card, CardHeader } from "./ui/card"
import { EllipsisVertical } from "lucide-react"

export default function ItemList({ items }: { items: Item[] }) {
return (
<div className="grid grid-cols-6 gap-5">
{
items.map((item: Item) => (
<Card key={item.id}>
<CardHeader>
<div className="flex flex-row gap-3 items-center">
<h1 className="flex-1">
{item.name}
</h1>
<EllipsisVertical />
</div>
</CardHeader>
</Card>
))
}
</div>
)
}

89 changes: 89 additions & 0 deletions src/components/SearchableSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import * as React from "react"
import { Check, ChevronsUpDown } from 'lucide-react'

import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
} from "@/components/ui/command"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
import { useSearchableSelect } from "@/hooks/use-searchable-select"

interface SearchableSelectProps {
options: Record<string, string>
onChange: (value: string) => void
placeholder?: string
}

export function SearchableSelect({
options,
onChange,
placeholder = "Select an item...",
}: SearchableSelectProps) {
const {
open,
setOpen,
value,
setValue,
search,
setSearch,
filteredOptions,
} = useSearchableSelect(options)

return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className="w-full justify-between"
>
{value ? options[value] : placeholder}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[--radix-popover-trigger-width] p-0">
<Command>
<CommandInput
placeholder="Search..."
value={search}
onValueChange={setSearch}
/>
<CommandEmpty>No item found.</CommandEmpty>
<CommandGroup>
{filteredOptions.map(([key, label]) => (
<CommandItem
key={key}
value={key}
onSelect={(currentValue) => {
setValue(currentValue === value ? "" : currentValue)
onChange(currentValue)
setOpen(false)
}}
>
<Check
className={cn(
"mr-2 h-4 w-4",
value === key ? "opacity-100" : "opacity-0"
)}
/>
{label}
</CommandItem>
))}
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
)
}


22 changes: 20 additions & 2 deletions src/components/forms/AddItemForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,28 @@ import { Input } from "../ui/input";
import { Button } from "../ui/button";
import { useState } from "react";
import { createItem } from "@/actions/items";
import { SearchableSelect } from "../SearchableSelect";

export default function AddItemForm() {
const [name, setName] = useState("")

const options = {
apple: "Apple",
banana: "Banana",
cherry: "Cherry",
date: "Date",
elderberry: "Elderberry",
fig: "Fig",
grape: "Grape",
}


const handleSubmit = async () => {
await createItem({
name: name,
order: 1000,
type: 'Lebensmittel'
type_id: "",
store_id: "",
is_favorite: false
}, ['/items'])
}

Expand All @@ -24,6 +37,11 @@ export default function AddItemForm() {
</CardHeader>
<CardContent>
<Input value={name} onChange={e => setName(e.currentTarget.value)} />
<SearchableSelect
options={options}
onChange={(value) => console.log("Selected:", value)}
placeholder="Select..."
/>
</CardContent>
<CardFooter>
<Button onClick={handleSubmit}>Add</Button>
Expand Down
Loading

0 comments on commit ec1745c

Please sign in to comment.