Skip to content

Commit

Permalink
Merge pull request #88 from kawalcovid19/feature/search
Browse files Browse the repository at this point in the history
Merge pull request #75 from kawalcovid19/feature/database-page
  • Loading branch information
zainfathoni authored Jul 17, 2021
2 parents 1efb4fb + bd1e731 commit 93464a0
Show file tree
Hide file tree
Showing 10 changed files with 182 additions and 79 deletions.
2 changes: 1 addition & 1 deletion components/contact-details.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Contact } from "../lib/database";
import { Contact } from "../lib/provinces";

type ContactDetailsProps = {
contact: Contact;
Expand Down
4 changes: 2 additions & 2 deletions components/contact-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
PhoneIcon,
} from "@heroicons/react/solid";
import { BadgeCheckIcon as BadgeCheckIconUnverified } from "@heroicons/react/outline";
import { Contact } from "../lib/database";
import { Contact } from "../lib/provinces";
import Link from "next/link";

type ContactListProps = {
Expand All @@ -19,7 +19,7 @@ export function ContactList(props: ContactListProps) {
<ul className="divide-y divide-gray-200">
{props.data.map((contact, index) => (
<li key={index}>
<Link href={`/database/${props.provinceSlug}/${index}`}>
<Link href={`/provinces/${props.provinceSlug}/${index}`}>
<a className="block hover:bg-gray-50">
<div className="px-4 py-4 sm:px-6">
<div className="flex items-center justify-between">
Expand Down
39 changes: 39 additions & 0 deletions components/province-list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import Link from "next/link";

export type ProvinceListItem = {
initials: string;
name: string;
slug: string;
count: number;
};

type ProvinceListProps = {
data: ProvinceListItem[];
};

export function ProvinceList(props: ProvinceListProps) {
return (
<ul className="mt-3 grid grid-cols-1 gap-5 sm:gap-6 sm:grid-cols-2 lg:grid-cols-3">
{props.data.map((province) => (
<li
key={province.name}
className="col-span-1 flex shadow-sm rounded-md"
>
<div className="bg-blue-500 flex-shrink-0 flex items-center justify-center w-16 text-white text-sm font-medium rounded-l-md">
{province.initials}
</div>
<Link href={`/provinces/${province.slug}`}>
<a className="flex-1 flex items-center justify-between border-t border-r border-b border-gray-200 bg-white rounded-r-md truncate">
<div className="flex-1 px-4 py-2 text-sm truncate">
<span className="text-gray-900 font-medium hover:text-gray-600">
{province.name}
</span>
<p className="text-gray-500">{province.count} Entri</p>
</div>
</a>
</Link>
</li>
))}
</ul>
);
}
39 changes: 39 additions & 0 deletions components/search-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
interface FormElements extends HTMLFormControlsCollection {
keywordsInput: HTMLInputElement;
}

interface UsernameFormElement extends HTMLFormElement {
readonly elements: FormElements;
}

export function SearchForm({
itemName,
onSubmitKeywords,
}: {
itemName: string;
onSubmitKeywords: (keywords: string) => void;
}) {
function handleSubmit(event: React.FormEvent<UsernameFormElement>) {
event.preventDefault();
onSubmitKeywords(event.currentTarget.elements.keywordsInput.value);
}

return (
<form className="flex items-center" onSubmit={handleSubmit}>
<label htmlFor="keywordsInput" className="flex-shrink-0">
Cari {itemName}:
</label>
<input
id="keywordsInput"
type="text"
className="focus:ring-indigo-500 focus:border-indigo-500 block w-full px-2 py-2 sm:text-sm border-gray-300 border-2 rounded-md my-5 mx-2"
/>
<button
type="submit"
className="bg-blue-600 text-white ml-2 py-2 px-6 rounded"
>
Cari
</button>
</form>
);
}
27 changes: 27 additions & 0 deletions lib/hooks/use-search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { useState } from "react";

export function useSearch<T = unknown[]>(items: T[], fieldNames: string[]) {
const [filteredItems, setFilteredItems] = useState<T[]>(items);
const handleSubmitKeywords = (keywords: string) => {
const lowerKeywords = keywords.toLowerCase();
setFilteredItems(
items.filter((item) => {
const filterBy = (fieldName: keyof T) => {
const selectedField = item[fieldName];

if (typeof selectedField === "string") {
return selectedField.toLowerCase().includes(lowerKeywords);
} else {
return false;
}
};

const filterFunctions = fieldNames.map((fieldName) =>
filterBy(fieldName as keyof T)
);
return filterFunctions.reduce((acc, curr) => acc || curr, false);
})
);
};
return [filteredItems, handleSubmitKeywords] as const;
}
10 changes: 5 additions & 5 deletions lib/database.ts → lib/provinces.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import database from "../data/wbw-sheets.json";
import provinces from "../data/wbw-sheets.json";
import { getSlug } from "./string-utils";

export type Database = Province[];
export type Provinces = Province[];

export type Province = {
readonly id: number;
Expand Down Expand Up @@ -29,7 +29,7 @@ export type ProvincePath = {
};

export const getProvincesPaths = (): ProvincePath[] =>
database.map((item, index) => {
provinces.map((item, index) => {
const provinceSlug = getSlug(item.name, index);
return {
params: { provinceSlug },
Expand All @@ -45,7 +45,7 @@ export type ContactPath = {

export const getContactsPaths = (): ContactPath[] => {
const contactsPaths: ContactPath[] = [];
database.forEach((province, provinceIndex) => {
provinces.forEach((province, provinceIndex) => {
province.data.forEach((_, contactIndex) => {
const provinceSlug = getSlug(province.name, provinceIndex);
contactsPaths.push({
Expand All @@ -56,4 +56,4 @@ export const getContactsPaths = (): ContactPath[] => {
return contactsPaths;
};

export default database as unknown as Database;
export default provinces as unknown as Provinces;
64 changes: 0 additions & 64 deletions pages/database/index.tsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { GetStaticPaths, GetStaticProps } from "next";
import { ContactDetails } from "../../../components/contact-details";
import database, { getContactsPaths, Contact } from "../../../lib/database";
import provinces, { getContactsPaths, Contact } from "../../../lib/provinces";
import { getTheLastSegmentFromKebabCase } from "../../../lib/string-utils";

type ContactPageProps = {
Expand All @@ -26,7 +26,7 @@ export const getStaticPaths: GetStaticPaths = () => {
export const getStaticProps: GetStaticProps = ({ params = {} }) => {
const { provinceSlug, contactSlug } = params;
const index = getTheLastSegmentFromKebabCase(provinceSlug as string);
const province = index ? database[index as unknown as number] : null;
const province = index ? provinces[index as unknown as number] : null;
const provinceName = province ? province.name : "";
const contact =
province !== null ? province.data[contactSlug as unknown as number] : null;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,42 @@
import { GetStaticPaths, GetStaticProps } from "next";
import { ContactList } from "../../../components/contact-list";
import database, { getProvincesPaths, Province } from "../../../lib/database";
import { SearchForm } from "../../../components/search-form";
import { useSearch } from "../../../lib/hooks/use-search";
import provinces, {
Contact,
getProvincesPaths,
Province,
} from "../../../lib/provinces";
import { getTheLastSegmentFromKebabCase } from "../../../lib/string-utils";

type ProvinceDatabaseProps = {
type ProvinceProps = {
province: Province;
provinceSlug: string;
};

export default function ProvinceDatabase(props: ProvinceDatabaseProps) {
export default function ProvincePage(props: ProvinceProps) {
const { province, provinceSlug } = props;
const [filteredContacts, handleSubmitKeywords] = useSearch(
props.province.data,
[
"kebutuhan",
"penyedia",
"lokasi",
"alamat",
"keterangan",
"kontak",
"tautan",
"tambahan_informasi",
"bentuk_verifikasi",
]
);

if (province) {
return (
<main>
<h1>Database for {province.name}</h1>
<ContactList data={province.data} provinceSlug={provinceSlug} />
<SearchForm itemName="kontak" onSubmitKeywords={handleSubmitKeywords} />
<ContactList data={filteredContacts} provinceSlug={provinceSlug} />
</main>
);
} else {
Expand All @@ -39,7 +60,7 @@ export const getStaticPaths: GetStaticPaths = () => {
export const getStaticProps: GetStaticProps = ({ params = {} }) => {
const { provinceSlug } = params;
const index = getTheLastSegmentFromKebabCase(provinceSlug as string);
const province = index ? database[index as unknown as number] : null;
const province = index ? provinces[index as unknown as number] : null;

return {
props: {
Expand Down
41 changes: 41 additions & 0 deletions pages/provinces/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { GetStaticProps } from "next";
import provinces from "../../lib/provinces";
import { getInitial, getSlug } from "../../lib/string-utils";
import { SearchForm } from "../../components/search-form";
import { ProvinceList, ProvinceListItem } from "../../components/province-list";
import { useSearch } from "../../lib/hooks/use-search";

type ProvincesPageProps = {
provincesList: ProvinceListItem[];
};

export default function ProvincesPage(props: ProvincesPageProps) {
const [filteredProvinces, handleSubmitKeywords] = useSearch(
props.provincesList,
["name"]
);
return (
<div>
<h2 className="text-gray-500 text-xs font-medium uppercase tracking-wide">
Daftar Provinsi
</h2>
<SearchForm itemName="provinsi" onSubmitKeywords={handleSubmitKeywords} />
<ProvinceList data={filteredProvinces} />
</div>
);
}

export const getStaticProps: GetStaticProps = () => {
const provincesList = provinces.map(({ name, data }, index) => ({
initials: getInitial(name),
name,
slug: getSlug(name, index),
count: data.length,
}));
provincesList.shift();
return {
props: {
provincesList,
},
};
};

0 comments on commit 93464a0

Please sign in to comment.