Skip to content

Commit

Permalink
Merge pull request #13 from sergio222-dev/develop
Browse files Browse the repository at this point in the history
added more filter options
  • Loading branch information
sergio222-dev authored Aug 11, 2024
2 parents b1ea4de + 77addfd commit ae9fc29
Show file tree
Hide file tree
Showing 11 changed files with 330 additions and 137 deletions.
78 changes: 3 additions & 75 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,78 +1,6 @@
# Qwik City App ⚡️

<img height="200" src="https://github.com/SAWARATSUKI/ServiceLogos/blob/main/Qwik.js/Qwik.png?raw=true" width="400"/>
<img height="200" src="https://github.com/SAWARATSUKI/ServiceLogos/blob/main/TypeScript/TypeScript.png?raw=true" width="400"/>

- [Qwik Docs](https://qwik.dev/)
- [Discord](https://qwik.dev/chat)
- [Qwik GitHub](https://github.com/BuilderIO/qwik)
- [@QwikDev](https://twitter.com/QwikDev)
- [Vite](https://vitejs.dev/)

---

## Project Structure

This project is using Qwik with [QwikCity](https://qwik.dev/qwikcity/overview/). QwikCity is just an extra set of tools on top of Qwik to make it easier to build a full site, including directory-based routing, layouts, and more.

Inside your project, you'll see the following directory structure:

```
├── public/
│ └── ...
└── src/
├── components/
│ └── ...
└── routes/
└── ...
```

- `src/routes`: Provides the directory-based routing, which can include a hierarchy of `layout.tsx` layout files, and an `index.tsx` file as the page. Additionally, `index.ts` files are endpoints. Please see the [routing docs](https://qwik.dev/qwikcity/routing/overview/) for more info.

- `src/components`: Recommended directory for components.

- `public`: Any static assets, like images, can be placed in the public directory. Please see the [Vite public directory](https://vitejs.dev/guide/assets.html#the-public-directory) for more info.

## Add Integrations and deployment

Use the `pnpm qwik add` command to add additional integrations. Some examples of integrations includes: Cloudflare, Netlify or Express Server, and the [Static Site Generator (SSG)](https://qwik.dev/qwikcity/guides/static-site-generation/).

```shell
pnpm qwik add # or `pnpm qwik add`
```

## Development

Development mode uses [Vite's development server](https://vitejs.dev/). The `dev` command will server-side render (SSR) the output during development.

```shell
npm start # or `pnpm start`
```

> Note: during dev mode, Vite may request a significant number of `.js` files. This does not represent a Qwik production build.
## Preview

The preview command will create a production build of the client modules, a production build of `src/entry.preview.tsx`, and run a local server. The preview server is only for convenience to preview a production build locally and should not be used as a production server.

```shell
pnpm preview # or `pnpm preview`
```

## Production

The production build will generate client and server modules by running both client and server build commands. The build command will use Typescript to run a type check on the source code.
# LDB

Generate types for database
```shell
pnpm build # or `pnpm build`
supabase gen types typescript --local > ./database.types.ts
```

## Fastify Server

This app has a minimal [Fastify server](https://fastify.dev/) implementation. After running a full build, you can preview the build using the command:

```
pnpm serve
```

Then visit [http://localhost:3000/](http://localhost:3000/)
4 changes: 2 additions & 2 deletions database.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,9 @@ export type Database = {
}
}
Views: {
deck_types: {
card_types: {
Row: {
subtype: string | null
name: string | null
}
Relationships: []
}
Expand Down
23 changes: 23 additions & 0 deletions src/components/accordion/Accordion.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { component$, Slot, useSignal } from "@builder.io/qwik";

interface AccordionProps {
title: string;
}

export const Accordion = component$<AccordionProps>(({ title }) => {

const isOpen = useSignal(false)

return (
<div class="rounded">
<hr />
<div class="flex items-center cursor-pointer hover:bg-primary hover:text-white hover:fill-white px-2 py-4" onClick$={() => isOpen.value = !isOpen.value}>
<p class="text-lg font-bold flex-1">{title}</p>
</div>
<div hidden={!isOpen.value} class="px-2">
<Slot/>
</div>
<hr />
</div>
)
});
13 changes: 11 additions & 2 deletions src/components/filterField/FilterField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ export const ChipFilter = component$<FilterChipProps>(({ ...props }) => {
});

interface FilterFieldProps {
onSubmit?: QRL<(this: FilterFieldProps, value: string | undefined) => Promise<void>>;
onSubmit?: QRL<(value: string | undefined) => Promise<void>>;
children?: JSXOutput[];
onClear?: QRL<() => Promise<void>>;
}

export const FilterField = component$<FilterFieldProps>(
({
onSubmit,
onClear,
}) => {
const r = useSignal<HTMLFormElement>();

Expand All @@ -40,19 +42,26 @@ export const FilterField = component$<FilterFieldProps>(
onSubmit?.(value?.toString());
})

const handleClear = $((e: KeyboardEvent) => {
if (e.key === 'Backspace' && (e.target as HTMLInputElement).value === '') {
onClear && onClear();
}
})

return (
<div class="relative w-full rounded-3xl p-2 ring-primary ring-4 focus-within:ring-secondary flex gap-2 flex-wrap">
<Slot/>
<form
ref={r}
class="w-full flex-1 min-w-[200px]"
class="w-full flex-1 min-w-[200px] items-center flex"
onSubmit$={handleSubmit}
preventdefault:submit>
<input
name="contains"
autocomplete="off"
class="focus:outline-none flex-1 w-full"
type="text"
onKeyDown$={handleClear}
/>
</form>
</div>
Expand Down
140 changes: 127 additions & 13 deletions src/features/cards/components/CardFilter.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,57 @@
import { $, component$, useContext } from '@builder.io/qwik';
import { ChipFilter, FilterField } from "~/components/filterField/FilterField";
import type { Filter } from "~/models/filters/Filter";
import { FILTERS_TYPES } from "~/models/filters/Filter";
import { FilterContext } from '~/stores/filterContext';
import { Pagination } from './Pagination';

// TODO: refactor this components, move to common components folder and remove all loaders and context
import { $, component$, useContext, useSignal } from '@builder.io/qwik';
import { Accordion } from "~/components/accordion/Accordion";
import { Button, ButtonIcon } from "~/components/button";
import { ChipFilter, FilterField } from "~/components/filterField/FilterField";
import { Icon } from "~/components/icons/Icon";
import type { Filter } from "~/models/filters/Filter";
import { FILTERS_TYPES } from "~/models/filters/Filter";
import { useSubtypeLoader } from "~/providers/loaders/cards";
import { FilterContext } from '~/stores/filterContext';
import { Pagination } from './Pagination';

const useCostFilters = () => {

// add const filters
const count = new Array(9).fill(0).map((_, i) => i + 1);

const countFilters: Filter[] = count.map(c => {
return {
id: `cost-${c}`,
label: `Cost: ${c}`,
value: c.toString(),
field: 'cost',
filterType: FILTERS_TYPES.IN,
}
})

return countFilters;
}

const useTypeFilters = (types: string[]) => {
const typeFilters: Filter[] = types.map(t => {
return {
id: `type-${t}`,
label: `Type: ${t}`,
value: t,
field: ['subtype', 'subtype2'],
filterType: FILTERS_TYPES.IN,
}
})

return typeFilters;
}

export const CardFilter = component$(() => {
// Loaders
const subtypes = useSubtypeLoader();

// Context
const c = useContext(FilterContext);
const c = useContext(FilterContext);
const refDialog = useSignal<HTMLDialogElement>();

// STATE
const costFilters = useCostFilters();
const typeFilters = useTypeFilters(subtypes.value);

// Handlers
const addContainsFilter = $(async (value: string | undefined) => {
Expand All @@ -20,22 +63,93 @@ export const CardFilter = component$(() => {
value: value,
field: 'text',
filterType: FILTERS_TYPES.LIKE,
isDisjunctive: true,
isContains: true,
}

// await c.addFilter(nameFilter);
await c.addFilter(textFilter);
})

const handleClearLastFilter = $(async () => {
if (c.filters.length === 0) return;
const lastFilter = c.filters[c.filters.length - 1];
void c.removeFilter(lastFilter.id)
})

const handleDialogOpen = $(() => {
refDialog.value?.showModal();
})

const handleDialogClose = $(() => {
refDialog.value?.close();
})

const isFilterInList = (filter: Filter) => {
return c.filters.find(f => f.id === filter.id);
}

const handleAddDialogFilter = $(async (filter: Filter) => {
// check if the filter is already in the list
const filterInList = c.filters.find(f => f.id === filter.id);
if (filterInList) {
void c.removeFilter(filterInList.id);
return;
}

await c.addFilter(filter);
});

// Render
return (
<div class="flex justify-between items-center">
<FilterField onSubmit={addContainsFilter}>
<div class="flex justify-between items-center gap-2">
<FilterField onSubmit={addContainsFilter} onClear={handleClearLastFilter}>
{c.filters.map(f => (
<ChipFilter key={f.id} onClick$={() => c.removeFilter(f.id)}>{f.label}</ChipFilter>
))}
</FilterField>
<Button onClick$={handleDialogOpen} class="px-2 py-3 flex items-baseline gap-2">
Filters
<Icon name="art" width={16} height={16} class="fill-primary"/>
</Button>
<dialog ref={refDialog} class="p-4 container max-w-xl">
<div class="flex gap-2">
<h2 class="text-xl flex-1">Filter</h2>
<div>
<ButtonIcon>
<Icon onClick$={handleDialogClose} name="close" width={16} height={16} class="fill-primary"/>
</ButtonIcon>
</div>
</div>
<div class="p-4">
<Accordion title="Cost">
<div class="flex flex-wrap gap-2">
{costFilters.map(f => (
<p
class={`hover:bg-primary hover:text-white ring-2 ring-primary cursor-pointer px-2 py-1
${isFilterInList(f) ? 'bg-primary text-white' : ''}`}
key={f.id}
onClick$={() => handleAddDialogFilter(f)}
>
{f.label}
</p>
))}
</div>
</Accordion>
<Accordion title="Type">
<div class="flex flex-wrap gap-2">
{typeFilters.map(f => (
<p
class={`hover:bg-primary hover:text-white ring-2 ring-primary cursor-pointer px-2 py-1
${isFilterInList(f) ? 'bg-primary text-white' : ''}`}
key={f.id}
onClick$={() => handleAddDialogFilter(f)}
>
{f.label}
</p>
))}
</div>
</Accordion>
</div>
</dialog>
<Pagination/>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion src/features/cards/components/Pagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const Pagination = component$(() => {

return (
<div>
<div class="flex items-center justify-between border-t border-gray-200 bg-white px-4 py-3 sm:px-6">
<div class="flex items-center justify-between border-t border-gray-200 bg-white py-2">
<div class="flex flex-1 justify-between sm:hidden">
<a
href="#"
Expand Down
2 changes: 1 addition & 1 deletion src/lib/supabase-qwik.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { RequestEvent, RequestEventBase, RequestEventLoader } from '@builder.io/qwik-city';
import { createBrowserClient, createServerClient } from '@supabase/ssr';
import { Database } from '../../database.types';
import { Database } from "../../database.types";

export function createClientBrowser() {
return createBrowserClient<Database>(
Expand Down
Loading

0 comments on commit ae9fc29

Please sign in to comment.