feat(i18n): localize inventory assets UI
This commit is contained in:
@@ -18,20 +18,23 @@ export default async function EditAssetPage({
|
||||
const recipients = await RecipientService.findAll()
|
||||
const asset = await AssetService.findById(assetId)
|
||||
const { dictionary } = await getI18n()
|
||||
const copy = dictionary.inventory.assets
|
||||
|
||||
if (!asset) {
|
||||
return <div>Asset 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 Asset</h1>
|
||||
<h1 className="text-2xl font-bold">{copy.edit.title}</h1>
|
||||
</div>
|
||||
<EditAssetForm
|
||||
items={items}
|
||||
recipients={recipients}
|
||||
asset={asset as unknown as AssetWithAssignment}
|
||||
formCopy={copy.form}
|
||||
statusCopy={copy.status}
|
||||
submitButtonCopy={dictionary.common.submitButton}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { Dictionary } from "@/i18n/dictionaries"
|
||||
|
||||
export type AssetListCopy = Dictionary["inventory"]["assets"]["list"]
|
||||
export type AssetFormCopy = Dictionary["inventory"]["assets"]["form"]
|
||||
export type AssetStatusCopy = Dictionary["inventory"]["assets"]["status"]
|
||||
export type AssetFallbackCopy = Dictionary["inventory"]["assets"]["fallback"]
|
||||
@@ -21,10 +21,14 @@ import type {
|
||||
UpdateAssetStatus,
|
||||
} from "@/types"
|
||||
|
||||
import type { AssetFormCopy, AssetStatusCopy } from "./asset.copy"
|
||||
|
||||
interface EditAssetFormProps {
|
||||
asset: AssetWithAssignment
|
||||
items: Item[]
|
||||
recipients: Recipient[]
|
||||
formCopy: AssetFormCopy
|
||||
statusCopy: AssetStatusCopy
|
||||
submitButtonCopy: SubmitButtonCopy
|
||||
}
|
||||
|
||||
@@ -32,6 +36,8 @@ export default function EditAssetForm({
|
||||
asset,
|
||||
items,
|
||||
recipients,
|
||||
formCopy,
|
||||
statusCopy,
|
||||
submitButtonCopy,
|
||||
}: EditAssetFormProps) {
|
||||
const router = useRouter()
|
||||
@@ -83,15 +89,15 @@ export default function EditAssetForm({
|
||||
<form className="flex flex-col gap-4" onSubmit={handleSubmit(onSubmit)}>
|
||||
<input type="hidden" {...register("id")} />
|
||||
<div>
|
||||
<label htmlFor="categoryId" className="mb-2 block text-lg">
|
||||
Item
|
||||
<label htmlFor="itemId" className="mb-2 block text-lg">
|
||||
{formCopy.itemLabel}
|
||||
</label>
|
||||
<select
|
||||
id="itemId"
|
||||
{...register("itemId")}
|
||||
className="w-full rounded-lg border px-4 py-2"
|
||||
>
|
||||
<option value="">Select a item:</option>
|
||||
<option value="">{formCopy.itemPlaceholder}</option>
|
||||
{items?.map((item) => (
|
||||
<option key={item.id} value={item.id}>
|
||||
{item.name}
|
||||
@@ -104,12 +110,12 @@ export default function EditAssetForm({
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="serialNumber" className="mb-2 block text-lg">
|
||||
Serial Number
|
||||
{formCopy.serialNumberLabel}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="serialNumber"
|
||||
placeholder="Serial number"
|
||||
placeholder={formCopy.serialNumberPlaceholder}
|
||||
{...register("serialNumber")}
|
||||
className="w-full rounded-lg border px-4 py-2"
|
||||
/>
|
||||
@@ -119,12 +125,12 @@ export default function EditAssetForm({
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="deliveryNote" className="mb-2 block text-lg">
|
||||
Delivery Note
|
||||
{formCopy.deliveryNoteLabel}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="deliveryNote"
|
||||
placeholder="Delivery note"
|
||||
placeholder={formCopy.deliveryNotePlaceholder}
|
||||
{...register("deliveryNote")}
|
||||
className="w-full rounded-lg border px-4 py-2"
|
||||
/>
|
||||
@@ -134,17 +140,17 @@ export default function EditAssetForm({
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="status" className="mb-2 block text-lg">
|
||||
Status
|
||||
{formCopy.statusLabel}
|
||||
</label>
|
||||
<select
|
||||
id="status"
|
||||
{...register("status")}
|
||||
className="w-full rounded-lg border px-4 py-2"
|
||||
>
|
||||
<option value="">Select a status</option>
|
||||
<option value="">{formCopy.statusPlaceholder}</option>
|
||||
{Object.values(ITEM_STATUS).map((status) => (
|
||||
<option key={status} value={status}>
|
||||
{status}
|
||||
{statusCopy[status]}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
@@ -155,14 +161,14 @@ export default function EditAssetForm({
|
||||
{status === "ASSIGNED" && (
|
||||
<div>
|
||||
<label htmlFor="recipientId" className="mb-2 block text-lg">
|
||||
Recipient
|
||||
{formCopy.recipientLabel}
|
||||
</label>
|
||||
<select
|
||||
id="recipientId"
|
||||
{...register("recipientId")}
|
||||
className="w-full rounded-lg border px-4 py-2"
|
||||
>
|
||||
<option value="">Select a Recipient</option>
|
||||
<option value="">{formCopy.recipientPlaceholder}</option>
|
||||
{recipients?.map((recipient) => (
|
||||
<option key={recipient.id} value={recipient.id}>
|
||||
{recipient.firstName} {recipient.lastName}
|
||||
@@ -179,7 +185,7 @@ export default function EditAssetForm({
|
||||
isSubmitting={isSubmitting}
|
||||
isSubmitSuccessful={isSubmitSuccessful}
|
||||
>
|
||||
Update Asset
|
||||
{formCopy.updateSubmit}
|
||||
</SubmitButton>
|
||||
</form>
|
||||
)
|
||||
|
||||
@@ -16,15 +16,21 @@ import {
|
||||
} from "@/schemas/asset.schema"
|
||||
import type { ItemWithoutStock, Recipient } from "@/types"
|
||||
|
||||
import type { AssetFormCopy, AssetStatusCopy } from "./asset.copy"
|
||||
|
||||
interface NewAssetFormProps {
|
||||
items: ItemWithoutStock[]
|
||||
recipients: Recipient[]
|
||||
formCopy: AssetFormCopy
|
||||
statusCopy: AssetStatusCopy
|
||||
submitButtonCopy: SubmitButtonCopy
|
||||
}
|
||||
|
||||
export default function NewAssetForm({
|
||||
items,
|
||||
recipients,
|
||||
formCopy,
|
||||
statusCopy,
|
||||
submitButtonCopy,
|
||||
}: NewAssetFormProps) {
|
||||
const router = useRouter()
|
||||
@@ -71,15 +77,15 @@ export default function NewAssetForm({
|
||||
<form className="flex flex-col gap-4" onSubmit={handleSubmit(onSubmit)}>
|
||||
<input type="hidden" {...register("id")} />
|
||||
<div>
|
||||
<label htmlFor="categoryId" className="mb-2 block text-lg">
|
||||
Item
|
||||
<label htmlFor="itemId" className="mb-2 block text-lg">
|
||||
{formCopy.itemLabel}
|
||||
</label>
|
||||
<select
|
||||
id="itemId"
|
||||
{...register("itemId")}
|
||||
className="w-full rounded-lg border px-4 py-2"
|
||||
>
|
||||
<option value="">Select a item:</option>
|
||||
<option value="">{formCopy.itemPlaceholder}</option>
|
||||
{items?.map((item) => (
|
||||
<option key={item.id} value={item.id}>
|
||||
{item.name}
|
||||
@@ -92,12 +98,12 @@ export default function NewAssetForm({
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="serialNumber" className="mb-2 block text-lg">
|
||||
Serial Number
|
||||
{formCopy.serialNumberLabel}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="serialNumber"
|
||||
placeholder="Serial number"
|
||||
placeholder={formCopy.serialNumberPlaceholder}
|
||||
{...register("serialNumber")}
|
||||
className="w-full rounded-lg border px-4 py-2"
|
||||
/>
|
||||
@@ -107,12 +113,12 @@ export default function NewAssetForm({
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="deliveryNote" className="mb-2 block text-lg">
|
||||
Delivery Note
|
||||
{formCopy.deliveryNoteLabel}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="deliveryNote"
|
||||
placeholder="Delivery note"
|
||||
placeholder={formCopy.deliveryNotePlaceholder}
|
||||
{...register("deliveryNote")}
|
||||
className="w-full rounded-lg border px-4 py-2"
|
||||
/>
|
||||
@@ -122,17 +128,17 @@ export default function NewAssetForm({
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="status" className="mb-2 block text-lg">
|
||||
Status
|
||||
{formCopy.statusLabel}
|
||||
</label>
|
||||
<select
|
||||
id="status"
|
||||
{...register("status")}
|
||||
className="w-full rounded-lg border px-4 py-2"
|
||||
>
|
||||
<option value="">Select a status</option>
|
||||
<option value="">{formCopy.statusPlaceholder}</option>
|
||||
{Object.values(ITEM_STATUS).map((status) => (
|
||||
<option key={status} value={status}>
|
||||
{status}
|
||||
{statusCopy[status]}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
@@ -143,14 +149,14 @@ export default function NewAssetForm({
|
||||
{status === "ASSIGNED" && (
|
||||
<div>
|
||||
<label htmlFor="recipientId" className="mb-2 block text-lg">
|
||||
Recipient
|
||||
{formCopy.recipientLabel}
|
||||
</label>
|
||||
<select
|
||||
id="recipientId"
|
||||
{...register("recipientId")}
|
||||
className="w-full rounded-lg border px-4 py-2"
|
||||
>
|
||||
<option value="">Select a Recipient</option>
|
||||
<option value="">{formCopy.recipientPlaceholder}</option>
|
||||
{recipients?.map((recipient) => (
|
||||
<option key={recipient.id} value={recipient.id}>
|
||||
{recipient.firstName} {recipient.lastName}
|
||||
@@ -167,7 +173,7 @@ export default function NewAssetForm({
|
||||
isSubmitting={isSubmitting}
|
||||
isSubmitSuccessful={isSubmitSuccessful}
|
||||
>
|
||||
Create Asset
|
||||
{formCopy.createSubmit}
|
||||
</SubmitButton>
|
||||
</form>
|
||||
)
|
||||
|
||||
@@ -10,15 +10,18 @@ export default async function NewAssetPage() {
|
||||
const items = await ItemService.findAllAssignable()
|
||||
const recipients = await RecipientService.findAll()
|
||||
const { dictionary } = await getI18n()
|
||||
const copy = dictionary.inventory.assets
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<h1 className="text-2xl font-bold">New Asset</h1>
|
||||
<h1 className="text-2xl font-bold">{copy.new.title}</h1>
|
||||
</div>
|
||||
<NewAssetForm
|
||||
items={items}
|
||||
recipients={recipients}
|
||||
formCopy={copy.form}
|
||||
statusCopy={copy.status}
|
||||
submitButtonCopy={dictionary.common.submitButton}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -4,8 +4,24 @@ import Link from "next/link"
|
||||
import PageHeader from "@/components/common/pageheader"
|
||||
import PaginationButtons from "@/components/common/pagination"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { getI18n } from "@/i18n/server"
|
||||
import { AssetService } from "@/services/asset.service"
|
||||
|
||||
import type {
|
||||
AssetFallbackCopy,
|
||||
AssetStatusCopy,
|
||||
} from "./_components/asset.copy"
|
||||
|
||||
function formatAssetStatus(
|
||||
status: string,
|
||||
statusCopy: AssetStatusCopy,
|
||||
fallbackCopy: AssetFallbackCopy,
|
||||
) {
|
||||
return status in statusCopy
|
||||
? statusCopy[status as keyof AssetStatusCopy]
|
||||
: fallbackCopy.unknownStatus
|
||||
}
|
||||
|
||||
export default async function AssetsPage(props: {
|
||||
searchParams?: Promise<{
|
||||
page?: string
|
||||
@@ -21,19 +37,22 @@ export default async function AssetsPage(props: {
|
||||
pageSize: 10,
|
||||
search,
|
||||
})
|
||||
const { dictionary } = await getI18n()
|
||||
const copy = dictionary.inventory.assets
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<PageHeader
|
||||
title="Assets"
|
||||
title={copy.list.title}
|
||||
link="/inventory/assets/new"
|
||||
data={assets}
|
||||
search={search}
|
||||
addLabel={copy.list.addLabel}
|
||||
/>
|
||||
{assets.length === 0 && currentPage === 1 && (
|
||||
<div className="flex gap-4">
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
No Assets found.
|
||||
{copy.list.empty}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -43,19 +62,19 @@ export default async function AssetsPage(props: {
|
||||
<thead className="border-b">
|
||||
<tr>
|
||||
<th scope="col" className="p-4">
|
||||
Item Name
|
||||
{copy.list.columns.item}
|
||||
</th>
|
||||
<th scope="col" className="p-4">
|
||||
Category
|
||||
{copy.list.columns.category}
|
||||
</th>
|
||||
<th scope="col" className="p-4">
|
||||
Serial Number
|
||||
{copy.list.columns.serialNumber}
|
||||
</th>
|
||||
<th scope="col" className="p-4">
|
||||
Status
|
||||
{copy.list.columns.status}
|
||||
</th>
|
||||
<th scope="col" className="p-4">
|
||||
Actions
|
||||
{copy.list.columns.actions}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -65,10 +84,20 @@ export default async function AssetsPage(props: {
|
||||
<td className="p-4">{asset.item?.name}</td>
|
||||
<td className="p-4">{asset.item?.category?.name}</td>
|
||||
<td className="p-4">{asset.serialNumber}</td>
|
||||
<td className="p-4">{asset.status}</td>
|
||||
<td className="p-4">
|
||||
{formatAssetStatus(
|
||||
asset.status,
|
||||
copy.status,
|
||||
copy.fallback,
|
||||
)}
|
||||
</td>
|
||||
<td className="flex items-center gap-2 p-4">
|
||||
<Link href={`/inventory/assets/${asset.id}/edit`} passHref>
|
||||
<Button variant="outline" size="icon">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
aria-label={copy.list.actions.edit}
|
||||
>
|
||||
<Pencil />
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
@@ -182,6 +182,56 @@ export const en = {
|
||||
itemRequired: "Item is required",
|
||||
},
|
||||
},
|
||||
assets: {
|
||||
list: {
|
||||
title: "Assets",
|
||||
addLabel: "Add Asset",
|
||||
empty: "No assets found.",
|
||||
columns: {
|
||||
item: "Item",
|
||||
category: "Category",
|
||||
serialNumber: "Serial Number",
|
||||
status: "Status",
|
||||
actions: "Actions",
|
||||
},
|
||||
actions: {
|
||||
edit: "Edit asset",
|
||||
},
|
||||
},
|
||||
new: {
|
||||
title: "New Asset",
|
||||
},
|
||||
edit: {
|
||||
title: "Edit Asset",
|
||||
notFound: "Asset not found",
|
||||
},
|
||||
form: {
|
||||
itemLabel: "Item",
|
||||
itemPlaceholder: "Select an item",
|
||||
serialNumberLabel: "Serial Number",
|
||||
serialNumberPlaceholder: "Serial number",
|
||||
deliveryNoteLabel: "Delivery Note",
|
||||
deliveryNotePlaceholder: "Delivery note",
|
||||
statusLabel: "Status",
|
||||
statusPlaceholder: "Select a status",
|
||||
recipientLabel: "Recipient",
|
||||
recipientPlaceholder: "Select a recipient",
|
||||
createSubmit: "Create Asset",
|
||||
updateSubmit: "Update Asset",
|
||||
},
|
||||
status: {
|
||||
AVAILABLE: "Available",
|
||||
ASSIGNED: "Assigned",
|
||||
RESERVED: "Reserved",
|
||||
IN_REPAIR: "In repair",
|
||||
BROKEN: "Broken",
|
||||
STOLEN: "Stolen",
|
||||
DISPOSED: "Disposed",
|
||||
},
|
||||
fallback: {
|
||||
unknownStatus: "Unknown status",
|
||||
},
|
||||
},
|
||||
},
|
||||
login: {
|
||||
title: "Sign In",
|
||||
|
||||
@@ -185,6 +185,56 @@ export const es = {
|
||||
itemRequired: "El artículo es obligatorio",
|
||||
},
|
||||
},
|
||||
assets: {
|
||||
list: {
|
||||
title: "Activos",
|
||||
addLabel: "Agregar activo",
|
||||
empty: "No se encontraron activos.",
|
||||
columns: {
|
||||
item: "Artículo",
|
||||
category: "Categoría",
|
||||
serialNumber: "Número de serie",
|
||||
status: "Estado",
|
||||
actions: "Acciones",
|
||||
},
|
||||
actions: {
|
||||
edit: "Editar activo",
|
||||
},
|
||||
},
|
||||
new: {
|
||||
title: "Nuevo activo",
|
||||
},
|
||||
edit: {
|
||||
title: "Editar activo",
|
||||
notFound: "Activo no encontrado",
|
||||
},
|
||||
form: {
|
||||
itemLabel: "Artículo",
|
||||
itemPlaceholder: "Selecciona un artículo",
|
||||
serialNumberLabel: "Número de serie",
|
||||
serialNumberPlaceholder: "Número de serie",
|
||||
deliveryNoteLabel: "Remito",
|
||||
deliveryNotePlaceholder: "Remito",
|
||||
statusLabel: "Estado",
|
||||
statusPlaceholder: "Selecciona un estado",
|
||||
recipientLabel: "Destinatario",
|
||||
recipientPlaceholder: "Selecciona un destinatario",
|
||||
createSubmit: "Crear activo",
|
||||
updateSubmit: "Actualizar activo",
|
||||
},
|
||||
status: {
|
||||
AVAILABLE: "Disponible",
|
||||
ASSIGNED: "Asignado",
|
||||
RESERVED: "Reservado",
|
||||
IN_REPAIR: "En reparación",
|
||||
BROKEN: "Roto",
|
||||
STOLEN: "Robado",
|
||||
DISPOSED: "Dado de baja",
|
||||
},
|
||||
fallback: {
|
||||
unknownStatus: "Estado desconocido",
|
||||
},
|
||||
},
|
||||
},
|
||||
login: {
|
||||
title: "Iniciar sesión",
|
||||
|
||||
Reference in New Issue
Block a user