Skip to content

Commit

Permalink
Merge pull request #75 from kawalcovid19/feature/database-page
Browse files Browse the repository at this point in the history
Add Database pages
  • Loading branch information
zainfathoni authored Jul 16, 2021
2 parents 4b3cf86 + 65757b5 commit 1efb4fb
Show file tree
Hide file tree
Showing 8 changed files with 437 additions and 2 deletions.
92 changes: 92 additions & 0 deletions components/contact-details.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { Contact } from "../lib/database";

type ContactDetailsProps = {
contact: Contact;
provinceName: string;
};

const ReportButton = () => (
<span className="ml-4 flex-shrink-0">
<button
type="button"
className="bg-white rounded-md font-medium text-indigo-600 hover:text-indigo-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Laporkan kesalahan
</button>
</span>
);

type DescriptionItemProps = {
label: string;
value?: string;
};

const DescriptionItem = (props: DescriptionItemProps) => (
<div className="py-4 sm:py-5 sm:grid sm:grid-cols-4 sm:gap-4">
<dt className="text-sm font-medium text-gray-500">{props.label}</dt>
<dd className="mt-1 flex text-sm text-gray-900 sm:mt-0 sm:col-span-3">
<span className="flex-grow">{props.value}</span>
<ReportButton />
</dd>
</div>
);

type DescriptionLinkProps = {
label: string;
value?: string;
};

const DescriptionLink = (props: DescriptionLinkProps) =>
props.value ? (
<div className="py-4 sm:py-5 sm:grid sm:grid-cols-4 sm:gap-4">
<dt className="text-sm font-medium text-gray-500">{props.label}</dt>
<dd className="mt-1 flex text-sm text-gray-900 sm:mt-0 sm:col-span-3">
<a
href={props.value}
target="_blank"
rel="noopener noreferrer"
className="flex-grow"
>
{props.value}
</a>
<ReportButton />
</dd>
</div>
) : null;

export function ContactDetails({ contact, provinceName }: ContactDetailsProps) {
return (
<>
<div>
<h3 className="text-lg leading-6 font-medium text-gray-900">
{provinceName}
</h3>
<p className="mt-1 max-w-2xl text-sm text-gray-500">
{contact.kebutuhan}
</p>
</div>
<div className="mt-5 border-t border-gray-200">
<dl className="divide-y divide-gray-200">
<DescriptionItem label="Penyedia" value={contact.penyedia} />
<DescriptionItem label="Keterangan" value={contact.keterangan} />
<DescriptionItem label="Lokasi" value={contact.lokasi} />
<DescriptionItem label="Kontak" value={contact.kontak} />
<DescriptionItem label="Alamat" value={contact.alamat} />
<DescriptionLink label="Tautan" value={contact.tautan} />
<DescriptionItem
label="Tanggal Verifikasi"
value={contact.tanggal_verifikasi}
/>
<DescriptionItem
label="Bentuk Verifikasi"
value={contact.bentuk_verifikasi}
/>
<DescriptionItem
label="Tambahan Informasi"
value={contact.tambahan_informasi}
/>
</dl>
</div>
</>
);
}
92 changes: 92 additions & 0 deletions components/contact-list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/* This example requires Tailwind CSS v2.0+ */
import {
BadgeCheckIcon,
LocationMarkerIcon,
PhoneIcon,
} from "@heroicons/react/solid";
import { BadgeCheckIcon as BadgeCheckIconUnverified } from "@heroicons/react/outline";
import { Contact } from "../lib/database";
import Link from "next/link";

type ContactListProps = {
data: Contact[];
provinceSlug: string;
};

export function ContactList(props: ContactListProps) {
return (
<div className="bg-white shadow overflow-hidden sm:rounded-md">
<ul className="divide-y divide-gray-200">
{props.data.map((contact, index) => (
<li key={index}>
<Link href={`/database/${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">
<p className="text-sm font-medium text-blue-600 truncate">
{contact.penyedia || contact.keterangan}
</p>
<div className="ml-2 flex-shrink-0 flex">
<p className="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-yellow-100 text-yellow-800">
{contact.kebutuhan}
</p>
</div>
</div>
<div className="mt-2 sm:flex sm:justify-between">
<p className="text-sm font-medium text-gray-600 truncate">
{contact.keterangan}
</p>
{contact.tanggal_verifikasi !== "" ? (
<div className="mt-2 flex items-center text-xs text-gray-500 sm:mt-0">
<p>
Terverifikasi pada{" "}
<time dateTime={contact.tanggal_verifikasi}>
{contact.tanggal_verifikasi}
</time>
</p>
<BadgeCheckIcon
className="flex-shrink-0 ml-1.5 h-5 w-5 text-green-400"
aria-hidden="true"
/>
</div>
) : (
<div className="mt-2 flex items-center text-xs text-gray-400 sm:mt-0">
<p>Belum terverifkasi</p>
<BadgeCheckIconUnverified
className="flex-shrink-0 ml-1.5 h-5 w-5 text-gray-400"
aria-hidden="true"
/>
</div>
)}
</div>
<div className="mt-2 sm:flex sm:justify-between">
<p className="flex items-center text-sm text-gray-500">
<PhoneIcon
className="flex-shrink-0 mr-1.5 h-5 w-5 text-gray-400"
aria-hidden="true"
/>
{contact.kontak}
</p>
</div>
<div className="mt-2 sm:flex sm:justify-between">
<div className="sm:flex">
{contact.alamat !== "" && (
<p className="mt-2 flex items-center text-sm text-gray-500 sm:mt-0">
<LocationMarkerIcon
className="flex-shrink-0 mr-1.5 h-5 w-5 text-gray-400"
aria-hidden="true"
/>
{contact.alamat}
</p>
)}
</div>
</div>
</div>
</a>
</Link>
</li>
))}
</ul>
</div>
);
}
59 changes: 59 additions & 0 deletions lib/database.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import database from "../data/wbw-sheets.json";
import { getSlug } from "./string-utils";

export type Database = Province[];

export type Province = {
readonly id: number;
readonly name: string;
readonly data: Contact[];
};

export type Contact = {
readonly kebutuhan?: string;
readonly keterangan?: string;
readonly lokasi?: string;
readonly penyedia?: string;
readonly kontak?: string;
readonly alamat?: string;
readonly tautan?: string;
readonly tambahan_informasi?: string;
readonly tanggal_verifikasi?: string;
readonly bentuk_verifikasi?: string;
};

export type ProvincePath = {
params: {
provinceSlug: string;
};
};

export const getProvincesPaths = (): ProvincePath[] =>
database.map((item, index) => {
const provinceSlug = getSlug(item.name, index);
return {
params: { provinceSlug },
};
});

export type ContactPath = {
params: {
provinceSlug: string;
contactSlug: string;
};
};

export const getContactsPaths = (): ContactPath[] => {
const contactsPaths: ContactPath[] = [];
database.forEach((province, provinceIndex) => {
province.data.forEach((_, contactIndex) => {
const provinceSlug = getSlug(province.name, provinceIndex);
contactsPaths.push({
params: { provinceSlug, contactSlug: contactIndex.toString() },
});
});
});
return contactsPaths;
};

export default database as unknown as Database;
38 changes: 38 additions & 0 deletions lib/string-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
export function replaceSpacesWithCamelCase(str: string): string {
return str.replace(/\s+/g, (s) => {
return s.substring(0, 1).toUpperCase() + s.substring(1);
});
}

export function removeSpaces(str: string): string {
return str.replace(/\s+/g, "");
}

export function convertToKebabCase(str: string): string {
return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
}

export function composeFunctions(...functions: Function[]): Function {
return (args: any) => {
return functions.reduce((acc, fn) => fn(acc), args);
};
}

export function getTheLastSegmentFromKebabCase(
str: string
): string | undefined {
return str.split("-").pop();
}

export function getSlug(name: string, index: number): string {
const kebabName = composeFunctions(
replaceSpacesWithCamelCase,
removeSpaces,
convertToKebabCase
)(name);
return `${kebabName}-${index}`;
}

export function getInitial(name: string) {
return name.charAt(0).toUpperCase();
}
40 changes: 40 additions & 0 deletions pages/database/[provinceSlug]/[contactSlug].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { GetStaticPaths, GetStaticProps } from "next";
import { ContactDetails } from "../../../components/contact-details";
import database, { getContactsPaths, Contact } from "../../../lib/database";
import { getTheLastSegmentFromKebabCase } from "../../../lib/string-utils";

type ContactPageProps = {
provinceName: string;
provinceSlug: string;
contact: Contact;
};

export default function ContactPage(props: ContactPageProps) {
return (
<ContactDetails contact={props.contact} provinceName={props.provinceName} />
);
}

export const getStaticPaths: GetStaticPaths = () => {
const paths = getContactsPaths();
return {
fallback: false,
paths,
};
};

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 provinceName = province ? province.name : "";
const contact =
province !== null ? province.data[contactSlug as unknown as number] : null;
return {
props: {
provinceSlug,
provinceName,
contact,
},
};
};
50 changes: 50 additions & 0 deletions pages/database/[provinceSlug]/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { GetStaticPaths, GetStaticProps } from "next";
import { ContactList } from "../../../components/contact-list";
import database, { getProvincesPaths, Province } from "../../../lib/database";
import { getTheLastSegmentFromKebabCase } from "../../../lib/string-utils";

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

export default function ProvinceDatabase(props: ProvinceDatabaseProps) {
const { province, provinceSlug } = props;

if (province) {
return (
<main>
<h1>Database for {province.name}</h1>
<ContactList data={province.data} provinceSlug={provinceSlug} />
</main>
);
} else {
return (
<main>
<h1>Database not found</h1>
</main>
);
}
}

export const getStaticPaths: GetStaticPaths = () => {
const paths = getProvincesPaths();

return {
fallback: false,
paths,
};
};

export const getStaticProps: GetStaticProps = ({ params = {} }) => {
const { provinceSlug } = params;
const index = getTheLastSegmentFromKebabCase(provinceSlug as string);
const province = index ? database[index as unknown as number] : null;

return {
props: {
province,
provinceSlug,
},
};
};
Loading

0 comments on commit 1efb4fb

Please sign in to comment.