feat: add /trainings page
- list of trainings - add button (taken from github: vojtechmares/website) - add currency formatter
This commit is contained in:
parent
40b68902f7
commit
623b018a1b
3 changed files with 201 additions and 0 deletions
9
lib/currency/formatter.ts
Normal file
9
lib/currency/formatter.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
export function formatCurrency(price: number | bigint, locale: string = 'en-US', currency: string = 'CZK'): string {
|
||||
const currencyFormatter = new Intl.NumberFormat(locale, {
|
||||
style: 'currency',
|
||||
currency: currency,
|
||||
maximumFractionDigits: 0,
|
||||
});
|
||||
|
||||
return currencyFormatter.format(price).replace(',', ' ')
|
||||
}
|
||||
78
src/components/Button.tsx
Normal file
78
src/components/Button.tsx
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import Link from "next/link";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
function classNames(...classes: any) {
|
||||
return classes.filter(Boolean).join(' ')
|
||||
}
|
||||
|
||||
const baseStyles = {
|
||||
solid:
|
||||
"group inline-flex items-center justify-center rounded-md font-semibold focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-2",
|
||||
outline:
|
||||
"group inline-flex ring-1 items-center justify-center rounded-md focus:outline-none",
|
||||
};
|
||||
|
||||
const variantStyles = {
|
||||
solid: {
|
||||
black:
|
||||
"bg-black text-white hover:bg-gray-700 active:bg-gray-800 focus-visible:outline-gray-900",
|
||||
amber:
|
||||
"bg-amber-500 text-white hover:bg-amber-600 active:bg-amber-800 focus-visible:outline-amber-500",
|
||||
white:
|
||||
"bg-white text-black hover:bg-amber-50 active:bg-amber-200 focus-visible:outline-white",
|
||||
},
|
||||
outline: {
|
||||
black:
|
||||
"ring-gray-200 text-black hover:ring-gray-300 active:bg-gray-100 focus-visible:outline-amber-500 focus-visible:ring-gray-300",
|
||||
white:
|
||||
"ring-gray-700 text-white hover:ring-gray-500 active:ring-gray-700 focus-visible:outline-white",
|
||||
amber: "", // Outline buttons cannot be amber
|
||||
},
|
||||
};
|
||||
|
||||
const transitionStyle = "transition duration-150 ease-in-out";
|
||||
|
||||
const sizeStyles = {
|
||||
medium: "px-4 py-2 text-sm",
|
||||
large: "px-8 py-4 text-base",
|
||||
};
|
||||
|
||||
type Props = {
|
||||
variant?: "solid" | "outline";
|
||||
color?: "black" | "white" | "amber";
|
||||
size?: "medium" | "large";
|
||||
className?: string;
|
||||
href?: string;
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
export function Button({
|
||||
variant = "solid",
|
||||
color = "black",
|
||||
size = "medium",
|
||||
className,
|
||||
href,
|
||||
children,
|
||||
}: Props) {
|
||||
if (variant === "outline" && color === "amber") {
|
||||
throw new Error("Outline buttons cannot be amber");
|
||||
}
|
||||
|
||||
className = classNames(
|
||||
baseStyles[variant],
|
||||
variantStyles[variant][color],
|
||||
sizeStyles[size],
|
||||
transitionStyle,
|
||||
className
|
||||
);
|
||||
|
||||
if (href !== undefined) {
|
||||
return (
|
||||
<Link href={href} className={className}>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
} else {
|
||||
return <button className={className}>{children}</button>;
|
||||
}
|
||||
}
|
||||
114
src/pages/trainings.tsx
Normal file
114
src/pages/trainings.tsx
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
import { GetServerSideProps } from "next";
|
||||
import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
|
||||
import { formatCurrency } from "~/lib/currency/formatter";
|
||||
|
||||
import { prisma } from "~/server/db";
|
||||
import { Training } from "lib/content/training";
|
||||
import { Layout } from "~/components/Layout";
|
||||
import { Button } from "~/components/Button";
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
|
||||
const trainings = await prisma.training.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
days: true,
|
||||
draft: true,
|
||||
priceOpen: true,
|
||||
priceCorporate: true,
|
||||
}
|
||||
});
|
||||
return { props: { trainings: trainings } };
|
||||
}
|
||||
|
||||
function Table({trainings}: { trainings: Training[] }) {
|
||||
return (
|
||||
<div className="px-4 sm:px-6 lg:px-8">
|
||||
<div className="sm:flex sm:items-center">
|
||||
<div className="sm:flex-auto">
|
||||
<h1 className="text-base font-semibold leading-6 text-gray-900">Trainings</h1>
|
||||
{/* <p className="mt-2 text-sm text-gray-700">
|
||||
A list of all the users in your account including their name, title, email and role.
|
||||
</p> */}
|
||||
</div>
|
||||
<div className="mt-4 sm:ml-16 sm:mt-0 sm:flex-none">
|
||||
{/* <button
|
||||
type="button"
|
||||
className="block rounded-md bg-indigo-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
|
||||
>
|
||||
Add user
|
||||
</button> */}
|
||||
<Button href="https://github.com/vojtechmares/backoffice/new/main/content/training">
|
||||
Add training
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-8 flow-root">
|
||||
<div className="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
||||
<div className="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
|
||||
<div className="overflow-hidden shadow ring-1 ring-black ring-opacity-5 sm:rounded-lg">
|
||||
<table className="min-w-full divide-y divide-gray-300">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
<th scope="col" className="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6">
|
||||
Name
|
||||
</th>
|
||||
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
Days
|
||||
</th>
|
||||
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
Draft
|
||||
</th>
|
||||
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
Price Open
|
||||
</th>
|
||||
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
Price Corporate
|
||||
</th>
|
||||
<th scope="col" className="relative py-3.5 pl-3 pr-4 sm:pr-6">
|
||||
<span className="sr-only">Edit</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-200 bg-white">
|
||||
{trainings.map((training) => (
|
||||
<tr key={training.id}>
|
||||
<td className="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-6">
|
||||
{training.name}
|
||||
</td>
|
||||
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">{training.days}</td>
|
||||
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">{training.draft ? 'Yes' : 'No'}</td>
|
||||
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">{formatCurrency(training.priceOpen)}</td>
|
||||
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">{formatCurrency(training.priceCorporate)}</td>
|
||||
<td className="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
|
||||
<Link href={"/trainings/" + training.id} className="text-black underline">
|
||||
Detail<span className="sr-only"> of {training.name}</span>
|
||||
</Link>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Training({ trainings }: { trainings: Training[] }) {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Training | MaresHQ backoffice</title>
|
||||
</Head>
|
||||
<Layout>
|
||||
{/* { JSON.stringify(trainings) } */}
|
||||
<Table trainings={trainings} />
|
||||
</Layout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in a new issue