feat(i18n): localize recipients UI
This commit is contained in:
@@ -9,21 +9,25 @@ export default async function RecipientEditPage({
|
||||
params: Promise<{ recipientId: string }>
|
||||
}) {
|
||||
const { recipientId } = await params
|
||||
const recipient = await RecipientService.findById(recipientId)
|
||||
const { dictionary } = await getI18n()
|
||||
const copy = dictionary.inventory.recipients
|
||||
const recipient = await RecipientService.findById(recipientId)
|
||||
|
||||
if (!recipient) {
|
||||
return <div>Recipient not found</div>
|
||||
return <div>{copy.edit.notFound}</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<h1 className="text-2xl font-bold">Edit Recipient</h1>
|
||||
<h1 className="text-2xl font-bold">{copy.edit.title}</h1>
|
||||
</div>
|
||||
<RecipientForm
|
||||
initialData={recipient}
|
||||
mode="edit"
|
||||
formCopy={copy.form}
|
||||
departmentCopy={copy.departments}
|
||||
fallbackCopy={copy.fallback}
|
||||
submitButtonCopy={dictionary.common.submitButton}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { getI18n } from "@/i18n/server"
|
||||
import { AssignmentService } from "@/services/assignment.service"
|
||||
import { RecipientService } from "@/services/recipient.service"
|
||||
|
||||
import { formatRecipientDepartment } from "../_components/recipient.copy"
|
||||
|
||||
export default async function RecipientInfoPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ recipientId: string }>
|
||||
}) {
|
||||
const { recipientId } = await params
|
||||
const { dictionary } = await getI18n()
|
||||
const copy = dictionary.inventory.recipients
|
||||
const recipient = await RecipientService.findById(recipientId)
|
||||
const assignments = await AssignmentService.findAllByRecipient(recipientId)
|
||||
|
||||
if (!recipient) {
|
||||
return <div>Recipient not found</div>
|
||||
return <div>{copy.detail.notFound}</div>
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -26,20 +31,30 @@ export default async function RecipientInfoPage({
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-2 gap-x-8 gap-y-2 text-sm">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">Username</span>
|
||||
<span className="text-gray-600">
|
||||
{copy.detail.labels.username}
|
||||
</span>
|
||||
<span>{recipient.username}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">Email</span>
|
||||
<span className="text-gray-600">{copy.detail.labels.email}</span>
|
||||
<span>{recipient.email}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">Phone</span>
|
||||
<span className="text-gray-600">{copy.detail.labels.phone}</span>
|
||||
<span>{recipient.phone}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">Department</span>
|
||||
<span>{recipient.department}</span>
|
||||
<span className="text-gray-600">
|
||||
{copy.detail.labels.department}
|
||||
</span>
|
||||
<span>
|
||||
{formatRecipientDepartment(
|
||||
recipient.department,
|
||||
copy.departments,
|
||||
copy.fallback,
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { Dictionary } from "@/i18n/dictionaries"
|
||||
|
||||
export type RecipientListCopy = Dictionary["inventory"]["recipients"]["list"]
|
||||
export type RecipientDetailCopy =
|
||||
Dictionary["inventory"]["recipients"]["detail"]
|
||||
export type RecipientFormCopy = Dictionary["inventory"]["recipients"]["form"]
|
||||
export type RecipientDepartmentCopy =
|
||||
Dictionary["inventory"]["recipients"]["departments"]
|
||||
export type RecipientFallbackCopy =
|
||||
Dictionary["inventory"]["recipients"]["fallback"]
|
||||
|
||||
export function formatRecipientDepartment(
|
||||
department: string | null | undefined,
|
||||
departmentCopy: RecipientDepartmentCopy,
|
||||
fallbackCopy: RecipientFallbackCopy,
|
||||
) {
|
||||
if (!department) {
|
||||
return fallbackCopy.unknownDepartment
|
||||
}
|
||||
|
||||
return department in departmentCopy
|
||||
? departmentCopy[department as keyof RecipientDepartmentCopy]
|
||||
: fallbackCopy.unknownDepartment
|
||||
}
|
||||
@@ -20,15 +20,28 @@ import {
|
||||
} from "@/schemas/recipient.schema"
|
||||
import type { Recipient } from "@/types"
|
||||
|
||||
import {
|
||||
formatRecipientDepartment,
|
||||
type RecipientDepartmentCopy,
|
||||
type RecipientFallbackCopy,
|
||||
type RecipientFormCopy,
|
||||
} from "./recipient.copy"
|
||||
|
||||
interface RecipientFormProps {
|
||||
initialData?: Recipient
|
||||
mode?: "create" | "edit"
|
||||
formCopy: RecipientFormCopy
|
||||
departmentCopy: RecipientDepartmentCopy
|
||||
fallbackCopy: RecipientFallbackCopy
|
||||
submitButtonCopy: SubmitButtonCopy
|
||||
}
|
||||
|
||||
export default function RecipientForm({
|
||||
initialData,
|
||||
mode = "create",
|
||||
formCopy,
|
||||
departmentCopy,
|
||||
fallbackCopy,
|
||||
submitButtonCopy,
|
||||
}: RecipientFormProps) {
|
||||
const router = useRouter()
|
||||
@@ -81,12 +94,12 @@ export default function RecipientForm({
|
||||
<input type="hidden" {...register("id")} />
|
||||
<div>
|
||||
<label htmlFor="username" className="mb-2 block text-lg">
|
||||
Username
|
||||
{formCopy.usernameLabel}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="username"
|
||||
placeholder="Username"
|
||||
placeholder={formCopy.usernamePlaceholder}
|
||||
{...register("username")}
|
||||
className={`w-full rounded-lg border px-4 py-2`}
|
||||
/>
|
||||
@@ -96,12 +109,12 @@ export default function RecipientForm({
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="firstName" className="mb-2 block text-lg">
|
||||
First Name
|
||||
{formCopy.firstNameLabel}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="firstName"
|
||||
placeholder="First Name"
|
||||
placeholder={formCopy.firstNamePlaceholder}
|
||||
{...register("firstName")}
|
||||
className={`w-full rounded-lg border px-4 py-2`}
|
||||
/>
|
||||
@@ -111,12 +124,12 @@ export default function RecipientForm({
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="lastName" className="mb-2 block text-lg">
|
||||
Last Name
|
||||
{formCopy.lastNameLabel}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="lastName"
|
||||
placeholder="Last Name"
|
||||
placeholder={formCopy.lastNamePlaceholder}
|
||||
{...register("lastName")}
|
||||
className={`w-full rounded-lg border px-4 py-2`}
|
||||
/>
|
||||
@@ -126,17 +139,21 @@ export default function RecipientForm({
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="department" className="mb-2 block text-lg">
|
||||
Department
|
||||
{formCopy.departmentLabel}
|
||||
</label>
|
||||
<select
|
||||
id="department"
|
||||
{...register("department")}
|
||||
className="w-full rounded-lg border px-4 py-2"
|
||||
>
|
||||
<option value="">Select a department</option>
|
||||
<option value="">{formCopy.departmentPlaceholder}</option>
|
||||
{Object.keys(RECIPIENT_DEPARTMENTS).map((department) => (
|
||||
<option key={department} value={department}>
|
||||
{department}
|
||||
{formatRecipientDepartment(
|
||||
department,
|
||||
departmentCopy,
|
||||
fallbackCopy,
|
||||
)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
@@ -146,12 +163,12 @@ export default function RecipientForm({
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="email" className="mb-2 block text-lg">
|
||||
Email
|
||||
{formCopy.emailLabel}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="email"
|
||||
placeholder="Email"
|
||||
placeholder={formCopy.emailPlaceholder}
|
||||
{...register("email")}
|
||||
className={`w-full rounded-lg border px-4 py-2`}
|
||||
/>
|
||||
@@ -159,12 +176,12 @@ export default function RecipientForm({
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="phone" className="mb-2 block text-lg">
|
||||
Phone
|
||||
{formCopy.phoneLabel}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="phone"
|
||||
placeholder="Phone"
|
||||
placeholder={formCopy.phonePlaceholder}
|
||||
{...register("phone")}
|
||||
className={`w-full rounded-lg border px-4 py-2`}
|
||||
/>
|
||||
@@ -175,7 +192,7 @@ export default function RecipientForm({
|
||||
isSubmitting={isSubmitting}
|
||||
isSubmitSuccessful={isSubmitSuccessful}
|
||||
>
|
||||
{mode === "create" ? "Create Recipient" : "Update Recipient"}
|
||||
{mode === "create" ? formCopy.createSubmit : formCopy.updateSubmit}
|
||||
</SubmitButton>
|
||||
</form>
|
||||
)
|
||||
|
||||
@@ -4,14 +4,18 @@ import RecipientForm from "../_components/recipient.form"
|
||||
|
||||
export default async function NewRecipientPage() {
|
||||
const { dictionary } = await getI18n()
|
||||
const copy = dictionary.inventory.recipients
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<h1 className="text-2xl font-bold">Add Recipient</h1>
|
||||
<h1 className="text-2xl font-bold">{copy.new.title}</h1>
|
||||
</div>
|
||||
<RecipientForm
|
||||
mode="create"
|
||||
formCopy={copy.form}
|
||||
departmentCopy={copy.departments}
|
||||
fallbackCopy={copy.fallback}
|
||||
submitButtonCopy={dictionary.common.submitButton}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -5,8 +5,11 @@ import PageHeader from "@/components/common/pageheader"
|
||||
import PaginationButtons from "@/components/common/pagination"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import type { Recipient } from "@/generated/prisma/client"
|
||||
import { getI18n } from "@/i18n/server"
|
||||
import { RecipientService } from "@/services/recipient.service"
|
||||
|
||||
import { formatRecipientDepartment } from "./_components/recipient.copy"
|
||||
|
||||
export default async function RecipientsPage(props: {
|
||||
searchParams?: Promise<{
|
||||
page?: string
|
||||
@@ -22,38 +25,41 @@ export default async function RecipientsPage(props: {
|
||||
pageSize: 10,
|
||||
search,
|
||||
})
|
||||
const { dictionary } = await getI18n()
|
||||
const copy = dictionary.inventory.recipients
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<PageHeader
|
||||
title="Recipients"
|
||||
title={copy.list.title}
|
||||
link="/recipients/new"
|
||||
addLabel={copy.list.addLabel}
|
||||
data={recipients}
|
||||
search={search}
|
||||
/>
|
||||
{recipients.length === 0 && <div>No recipients found</div>}
|
||||
{recipients.length === 0 && <div>{copy.list.empty}</div>}
|
||||
{recipients.length > 0 && (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="text-muted-foreground w-full text-left text-sm">
|
||||
<thead className="border-b">
|
||||
<tr>
|
||||
<th scope="col" className="p-4">
|
||||
Username
|
||||
{copy.list.columns.username}
|
||||
</th>
|
||||
<th scope="col" className="p-4">
|
||||
Name
|
||||
{copy.list.columns.name}
|
||||
</th>
|
||||
<th scope="col" className="p-4">
|
||||
Email
|
||||
{copy.list.columns.email}
|
||||
</th>
|
||||
<th scope="col" className="p-4">
|
||||
Phone
|
||||
{copy.list.columns.phone}
|
||||
</th>
|
||||
<th scope="col" className="p-4">
|
||||
Department
|
||||
{copy.list.columns.department}
|
||||
</th>
|
||||
<th scope="col" className="p-4">
|
||||
Actions
|
||||
{copy.list.columns.actions}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -66,10 +72,20 @@ export default async function RecipientsPage(props: {
|
||||
</td>
|
||||
<td className="p-4">{recipient.email}</td>
|
||||
<td className="p-4">{recipient.phone}</td>
|
||||
<td className="p-4">{recipient.department}</td>
|
||||
<td className="p-4">
|
||||
{formatRecipientDepartment(
|
||||
recipient.department,
|
||||
copy.departments,
|
||||
copy.fallback,
|
||||
)}
|
||||
</td>
|
||||
<td className="flex items-center gap-2 p-4">
|
||||
<Link href={`/recipients/${recipient.id}`} passHref>
|
||||
<Button variant="outline" size="icon">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
aria-label={copy.list.actions.view}
|
||||
>
|
||||
<Eye />
|
||||
</Button>
|
||||
</Link>
|
||||
@@ -78,6 +94,7 @@ export default async function RecipientsPage(props: {
|
||||
className="btn btn-primary"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
aria-label={copy.list.actions.edit}
|
||||
>
|
||||
<Pencil />
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user