-
Notifications
You must be signed in to change notification settings - Fork 165
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #75 from kawalcovid19/feature/database-page
Add Database pages
- Loading branch information
Showing
8 changed files
with
437 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}, | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}, | ||
}; | ||
}; |
Oops, something went wrong.