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 recipients = await RecipientService.findAll()
|
||||||
const asset = await AssetService.findById(assetId)
|
const asset = await AssetService.findById(assetId)
|
||||||
const { dictionary } = await getI18n()
|
const { dictionary } = await getI18n()
|
||||||
|
const copy = dictionary.inventory.assets
|
||||||
|
|
||||||
if (!asset) {
|
if (!asset) {
|
||||||
return <div>Asset not found</div>
|
return <div>{copy.edit.notFound}</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex items-center justify-between 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>
|
</div>
|
||||||
<EditAssetForm
|
<EditAssetForm
|
||||||
items={items}
|
items={items}
|
||||||
recipients={recipients}
|
recipients={recipients}
|
||||||
asset={asset as unknown as AssetWithAssignment}
|
asset={asset as unknown as AssetWithAssignment}
|
||||||
|
formCopy={copy.form}
|
||||||
|
statusCopy={copy.status}
|
||||||
submitButtonCopy={dictionary.common.submitButton}
|
submitButtonCopy={dictionary.common.submitButton}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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,
|
UpdateAssetStatus,
|
||||||
} from "@/types"
|
} from "@/types"
|
||||||
|
|
||||||
|
import type { AssetFormCopy, AssetStatusCopy } from "./asset.copy"
|
||||||
|
|
||||||
interface EditAssetFormProps {
|
interface EditAssetFormProps {
|
||||||
asset: AssetWithAssignment
|
asset: AssetWithAssignment
|
||||||
items: Item[]
|
items: Item[]
|
||||||
recipients: Recipient[]
|
recipients: Recipient[]
|
||||||
|
formCopy: AssetFormCopy
|
||||||
|
statusCopy: AssetStatusCopy
|
||||||
submitButtonCopy: SubmitButtonCopy
|
submitButtonCopy: SubmitButtonCopy
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,6 +36,8 @@ export default function EditAssetForm({
|
|||||||
asset,
|
asset,
|
||||||
items,
|
items,
|
||||||
recipients,
|
recipients,
|
||||||
|
formCopy,
|
||||||
|
statusCopy,
|
||||||
submitButtonCopy,
|
submitButtonCopy,
|
||||||
}: EditAssetFormProps) {
|
}: EditAssetFormProps) {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -83,15 +89,15 @@ export default function EditAssetForm({
|
|||||||
<form className="flex flex-col gap-4" onSubmit={handleSubmit(onSubmit)}>
|
<form className="flex flex-col gap-4" onSubmit={handleSubmit(onSubmit)}>
|
||||||
<input type="hidden" {...register("id")} />
|
<input type="hidden" {...register("id")} />
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="categoryId" className="mb-2 block text-lg">
|
<label htmlFor="itemId" className="mb-2 block text-lg">
|
||||||
Item
|
{formCopy.itemLabel}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
id="itemId"
|
id="itemId"
|
||||||
{...register("itemId")}
|
{...register("itemId")}
|
||||||
className="w-full rounded-lg border px-4 py-2"
|
className="w-full rounded-lg border px-4 py-2"
|
||||||
>
|
>
|
||||||
<option value="">Select a item:</option>
|
<option value="">{formCopy.itemPlaceholder}</option>
|
||||||
{items?.map((item) => (
|
{items?.map((item) => (
|
||||||
<option key={item.id} value={item.id}>
|
<option key={item.id} value={item.id}>
|
||||||
{item.name}
|
{item.name}
|
||||||
@@ -104,12 +110,12 @@ export default function EditAssetForm({
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="serialNumber" className="mb-2 block text-lg">
|
<label htmlFor="serialNumber" className="mb-2 block text-lg">
|
||||||
Serial Number
|
{formCopy.serialNumberLabel}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="serialNumber"
|
id="serialNumber"
|
||||||
placeholder="Serial number"
|
placeholder={formCopy.serialNumberPlaceholder}
|
||||||
{...register("serialNumber")}
|
{...register("serialNumber")}
|
||||||
className="w-full rounded-lg border px-4 py-2"
|
className="w-full rounded-lg border px-4 py-2"
|
||||||
/>
|
/>
|
||||||
@@ -119,12 +125,12 @@ export default function EditAssetForm({
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="deliveryNote" className="mb-2 block text-lg">
|
<label htmlFor="deliveryNote" className="mb-2 block text-lg">
|
||||||
Delivery Note
|
{formCopy.deliveryNoteLabel}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="deliveryNote"
|
id="deliveryNote"
|
||||||
placeholder="Delivery note"
|
placeholder={formCopy.deliveryNotePlaceholder}
|
||||||
{...register("deliveryNote")}
|
{...register("deliveryNote")}
|
||||||
className="w-full rounded-lg border px-4 py-2"
|
className="w-full rounded-lg border px-4 py-2"
|
||||||
/>
|
/>
|
||||||
@@ -134,17 +140,17 @@ export default function EditAssetForm({
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="status" className="mb-2 block text-lg">
|
<label htmlFor="status" className="mb-2 block text-lg">
|
||||||
Status
|
{formCopy.statusLabel}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
id="status"
|
id="status"
|
||||||
{...register("status")}
|
{...register("status")}
|
||||||
className="w-full rounded-lg border px-4 py-2"
|
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) => (
|
{Object.values(ITEM_STATUS).map((status) => (
|
||||||
<option key={status} value={status}>
|
<option key={status} value={status}>
|
||||||
{status}
|
{statusCopy[status]}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
@@ -155,14 +161,14 @@ export default function EditAssetForm({
|
|||||||
{status === "ASSIGNED" && (
|
{status === "ASSIGNED" && (
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="recipientId" className="mb-2 block text-lg">
|
<label htmlFor="recipientId" className="mb-2 block text-lg">
|
||||||
Recipient
|
{formCopy.recipientLabel}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
id="recipientId"
|
id="recipientId"
|
||||||
{...register("recipientId")}
|
{...register("recipientId")}
|
||||||
className="w-full rounded-lg border px-4 py-2"
|
className="w-full rounded-lg border px-4 py-2"
|
||||||
>
|
>
|
||||||
<option value="">Select a Recipient</option>
|
<option value="">{formCopy.recipientPlaceholder}</option>
|
||||||
{recipients?.map((recipient) => (
|
{recipients?.map((recipient) => (
|
||||||
<option key={recipient.id} value={recipient.id}>
|
<option key={recipient.id} value={recipient.id}>
|
||||||
{recipient.firstName} {recipient.lastName}
|
{recipient.firstName} {recipient.lastName}
|
||||||
@@ -179,7 +185,7 @@ export default function EditAssetForm({
|
|||||||
isSubmitting={isSubmitting}
|
isSubmitting={isSubmitting}
|
||||||
isSubmitSuccessful={isSubmitSuccessful}
|
isSubmitSuccessful={isSubmitSuccessful}
|
||||||
>
|
>
|
||||||
Update Asset
|
{formCopy.updateSubmit}
|
||||||
</SubmitButton>
|
</SubmitButton>
|
||||||
</form>
|
</form>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -16,15 +16,21 @@ import {
|
|||||||
} from "@/schemas/asset.schema"
|
} from "@/schemas/asset.schema"
|
||||||
import type { ItemWithoutStock, Recipient } from "@/types"
|
import type { ItemWithoutStock, Recipient } from "@/types"
|
||||||
|
|
||||||
|
import type { AssetFormCopy, AssetStatusCopy } from "./asset.copy"
|
||||||
|
|
||||||
interface NewAssetFormProps {
|
interface NewAssetFormProps {
|
||||||
items: ItemWithoutStock[]
|
items: ItemWithoutStock[]
|
||||||
recipients: Recipient[]
|
recipients: Recipient[]
|
||||||
|
formCopy: AssetFormCopy
|
||||||
|
statusCopy: AssetStatusCopy
|
||||||
submitButtonCopy: SubmitButtonCopy
|
submitButtonCopy: SubmitButtonCopy
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function NewAssetForm({
|
export default function NewAssetForm({
|
||||||
items,
|
items,
|
||||||
recipients,
|
recipients,
|
||||||
|
formCopy,
|
||||||
|
statusCopy,
|
||||||
submitButtonCopy,
|
submitButtonCopy,
|
||||||
}: NewAssetFormProps) {
|
}: NewAssetFormProps) {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -71,15 +77,15 @@ export default function NewAssetForm({
|
|||||||
<form className="flex flex-col gap-4" onSubmit={handleSubmit(onSubmit)}>
|
<form className="flex flex-col gap-4" onSubmit={handleSubmit(onSubmit)}>
|
||||||
<input type="hidden" {...register("id")} />
|
<input type="hidden" {...register("id")} />
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="categoryId" className="mb-2 block text-lg">
|
<label htmlFor="itemId" className="mb-2 block text-lg">
|
||||||
Item
|
{formCopy.itemLabel}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
id="itemId"
|
id="itemId"
|
||||||
{...register("itemId")}
|
{...register("itemId")}
|
||||||
className="w-full rounded-lg border px-4 py-2"
|
className="w-full rounded-lg border px-4 py-2"
|
||||||
>
|
>
|
||||||
<option value="">Select a item:</option>
|
<option value="">{formCopy.itemPlaceholder}</option>
|
||||||
{items?.map((item) => (
|
{items?.map((item) => (
|
||||||
<option key={item.id} value={item.id}>
|
<option key={item.id} value={item.id}>
|
||||||
{item.name}
|
{item.name}
|
||||||
@@ -92,12 +98,12 @@ export default function NewAssetForm({
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="serialNumber" className="mb-2 block text-lg">
|
<label htmlFor="serialNumber" className="mb-2 block text-lg">
|
||||||
Serial Number
|
{formCopy.serialNumberLabel}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="serialNumber"
|
id="serialNumber"
|
||||||
placeholder="Serial number"
|
placeholder={formCopy.serialNumberPlaceholder}
|
||||||
{...register("serialNumber")}
|
{...register("serialNumber")}
|
||||||
className="w-full rounded-lg border px-4 py-2"
|
className="w-full rounded-lg border px-4 py-2"
|
||||||
/>
|
/>
|
||||||
@@ -107,12 +113,12 @@ export default function NewAssetForm({
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="deliveryNote" className="mb-2 block text-lg">
|
<label htmlFor="deliveryNote" className="mb-2 block text-lg">
|
||||||
Delivery Note
|
{formCopy.deliveryNoteLabel}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="deliveryNote"
|
id="deliveryNote"
|
||||||
placeholder="Delivery note"
|
placeholder={formCopy.deliveryNotePlaceholder}
|
||||||
{...register("deliveryNote")}
|
{...register("deliveryNote")}
|
||||||
className="w-full rounded-lg border px-4 py-2"
|
className="w-full rounded-lg border px-4 py-2"
|
||||||
/>
|
/>
|
||||||
@@ -122,17 +128,17 @@ export default function NewAssetForm({
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="status" className="mb-2 block text-lg">
|
<label htmlFor="status" className="mb-2 block text-lg">
|
||||||
Status
|
{formCopy.statusLabel}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
id="status"
|
id="status"
|
||||||
{...register("status")}
|
{...register("status")}
|
||||||
className="w-full rounded-lg border px-4 py-2"
|
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) => (
|
{Object.values(ITEM_STATUS).map((status) => (
|
||||||
<option key={status} value={status}>
|
<option key={status} value={status}>
|
||||||
{status}
|
{statusCopy[status]}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
@@ -143,14 +149,14 @@ export default function NewAssetForm({
|
|||||||
{status === "ASSIGNED" && (
|
{status === "ASSIGNED" && (
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="recipientId" className="mb-2 block text-lg">
|
<label htmlFor="recipientId" className="mb-2 block text-lg">
|
||||||
Recipient
|
{formCopy.recipientLabel}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
id="recipientId"
|
id="recipientId"
|
||||||
{...register("recipientId")}
|
{...register("recipientId")}
|
||||||
className="w-full rounded-lg border px-4 py-2"
|
className="w-full rounded-lg border px-4 py-2"
|
||||||
>
|
>
|
||||||
<option value="">Select a Recipient</option>
|
<option value="">{formCopy.recipientPlaceholder}</option>
|
||||||
{recipients?.map((recipient) => (
|
{recipients?.map((recipient) => (
|
||||||
<option key={recipient.id} value={recipient.id}>
|
<option key={recipient.id} value={recipient.id}>
|
||||||
{recipient.firstName} {recipient.lastName}
|
{recipient.firstName} {recipient.lastName}
|
||||||
@@ -167,7 +173,7 @@ export default function NewAssetForm({
|
|||||||
isSubmitting={isSubmitting}
|
isSubmitting={isSubmitting}
|
||||||
isSubmitSuccessful={isSubmitSuccessful}
|
isSubmitSuccessful={isSubmitSuccessful}
|
||||||
>
|
>
|
||||||
Create Asset
|
{formCopy.createSubmit}
|
||||||
</SubmitButton>
|
</SubmitButton>
|
||||||
</form>
|
</form>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,15 +10,18 @@ export default async function NewAssetPage() {
|
|||||||
const items = await ItemService.findAllAssignable()
|
const items = await ItemService.findAllAssignable()
|
||||||
const recipients = await RecipientService.findAll()
|
const recipients = await RecipientService.findAll()
|
||||||
const { dictionary } = await getI18n()
|
const { dictionary } = await getI18n()
|
||||||
|
const copy = dictionary.inventory.assets
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex items-center justify-between 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>
|
</div>
|
||||||
<NewAssetForm
|
<NewAssetForm
|
||||||
items={items}
|
items={items}
|
||||||
recipients={recipients}
|
recipients={recipients}
|
||||||
|
formCopy={copy.form}
|
||||||
|
statusCopy={copy.status}
|
||||||
submitButtonCopy={dictionary.common.submitButton}
|
submitButtonCopy={dictionary.common.submitButton}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,8 +4,24 @@ import Link from "next/link"
|
|||||||
import PageHeader from "@/components/common/pageheader"
|
import PageHeader from "@/components/common/pageheader"
|
||||||
import PaginationButtons from "@/components/common/pagination"
|
import PaginationButtons from "@/components/common/pagination"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { getI18n } from "@/i18n/server"
|
||||||
import { AssetService } from "@/services/asset.service"
|
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: {
|
export default async function AssetsPage(props: {
|
||||||
searchParams?: Promise<{
|
searchParams?: Promise<{
|
||||||
page?: string
|
page?: string
|
||||||
@@ -21,19 +37,22 @@ export default async function AssetsPage(props: {
|
|||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
search,
|
search,
|
||||||
})
|
})
|
||||||
|
const { dictionary } = await getI18n()
|
||||||
|
const copy = dictionary.inventory.assets
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title="Assets"
|
title={copy.list.title}
|
||||||
link="/inventory/assets/new"
|
link="/inventory/assets/new"
|
||||||
data={assets}
|
data={assets}
|
||||||
search={search}
|
search={search}
|
||||||
|
addLabel={copy.list.addLabel}
|
||||||
/>
|
/>
|
||||||
{assets.length === 0 && currentPage === 1 && (
|
{assets.length === 0 && currentPage === 1 && (
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
<div className="flex items-center justify-between gap-4">
|
<div className="flex items-center justify-between gap-4">
|
||||||
No Assets found.
|
{copy.list.empty}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -43,19 +62,19 @@ export default async function AssetsPage(props: {
|
|||||||
<thead className="border-b">
|
<thead className="border-b">
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col" className="p-4">
|
<th scope="col" className="p-4">
|
||||||
Item Name
|
{copy.list.columns.item}
|
||||||
</th>
|
</th>
|
||||||
<th scope="col" className="p-4">
|
<th scope="col" className="p-4">
|
||||||
Category
|
{copy.list.columns.category}
|
||||||
</th>
|
</th>
|
||||||
<th scope="col" className="p-4">
|
<th scope="col" className="p-4">
|
||||||
Serial Number
|
{copy.list.columns.serialNumber}
|
||||||
</th>
|
</th>
|
||||||
<th scope="col" className="p-4">
|
<th scope="col" className="p-4">
|
||||||
Status
|
{copy.list.columns.status}
|
||||||
</th>
|
</th>
|
||||||
<th scope="col" className="p-4">
|
<th scope="col" className="p-4">
|
||||||
Actions
|
{copy.list.columns.actions}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</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?.name}</td>
|
||||||
<td className="p-4">{asset.item?.category?.name}</td>
|
<td className="p-4">{asset.item?.category?.name}</td>
|
||||||
<td className="p-4">{asset.serialNumber}</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">
|
<td className="flex items-center gap-2 p-4">
|
||||||
<Link href={`/inventory/assets/${asset.id}/edit`} passHref>
|
<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 />
|
<Pencil />
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -182,6 +182,56 @@ export const en = {
|
|||||||
itemRequired: "Item is required",
|
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: {
|
login: {
|
||||||
title: "Sign In",
|
title: "Sign In",
|
||||||
|
|||||||
@@ -185,6 +185,56 @@ export const es = {
|
|||||||
itemRequired: "El artículo es obligatorio",
|
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: {
|
login: {
|
||||||
title: "Iniciar sesión",
|
title: "Iniciar sesión",
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import { expect, type Page, test } from "@playwright/test"
|
||||||
|
|
||||||
|
async function setLocaleCookie(
|
||||||
|
page: Page,
|
||||||
|
locale: "en" | "es",
|
||||||
|
baseURL?: string,
|
||||||
|
) {
|
||||||
|
await page.context().addCookies([
|
||||||
|
{
|
||||||
|
name: "stock-manager-locale",
|
||||||
|
value: locale,
|
||||||
|
url: baseURL ?? "http://127.0.0.1:3100",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
async function signInAsAdmin(page: Page, baseURL?: string) {
|
||||||
|
await setLocaleCookie(page, "en", baseURL)
|
||||||
|
await page.goto("/login")
|
||||||
|
await page.getByLabel("Username").fill("admin")
|
||||||
|
await page.getByLabel("Password").fill("admin-password")
|
||||||
|
await page.getByRole("button", { name: "Sign In" }).click()
|
||||||
|
await expect(page).toHaveURL("/")
|
||||||
|
}
|
||||||
|
|
||||||
|
test.describe("inventory assets localization", () => {
|
||||||
|
test("renders asset list and new form UI/status copy in Spanish", async ({
|
||||||
|
baseURL,
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await signInAsAdmin(page, baseURL)
|
||||||
|
await setLocaleCookie(page, "es", baseURL)
|
||||||
|
|
||||||
|
await page.goto("/inventory/assets")
|
||||||
|
|
||||||
|
await expect(page.locator("html")).toHaveAttribute("lang", "es")
|
||||||
|
await expect(page.getByRole("heading", { name: "Activos" })).toBeVisible()
|
||||||
|
await expect(
|
||||||
|
page.getByRole("link", { name: /Agregar activo/ }),
|
||||||
|
).toBeVisible()
|
||||||
|
await expect(page.getByText("No se encontraron activos.")).toBeVisible()
|
||||||
|
|
||||||
|
await page.goto("/inventory/assets/new")
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByRole("heading", { name: "Nuevo activo" }),
|
||||||
|
).toBeVisible()
|
||||||
|
await expect(page.getByLabel("Artículo")).toBeVisible()
|
||||||
|
await expect(page.locator("select#itemId")).toContainText(
|
||||||
|
"Selecciona un artículo",
|
||||||
|
)
|
||||||
|
await expect(page.getByLabel("Número de serie")).toBeVisible()
|
||||||
|
await expect(page.getByPlaceholder("Número de serie")).toBeVisible()
|
||||||
|
await expect(page.getByLabel("Remito")).toBeVisible()
|
||||||
|
await expect(page.getByPlaceholder("Remito")).toBeVisible()
|
||||||
|
await expect(page.getByLabel("Estado")).toBeVisible()
|
||||||
|
await expect(page.locator("select#status")).toContainText("Disponible")
|
||||||
|
await expect(page.locator("select#status")).toContainText("Asignado")
|
||||||
|
await expect(page.locator("option[value='AVAILABLE']")).toHaveText(
|
||||||
|
"Disponible",
|
||||||
|
)
|
||||||
|
await expect(page.locator("option[value='ASSIGNED']")).toHaveText(
|
||||||
|
"Asignado",
|
||||||
|
)
|
||||||
|
await expect(
|
||||||
|
page.getByRole("button", { name: "Crear activo" }),
|
||||||
|
).toBeVisible()
|
||||||
|
await expect(
|
||||||
|
page.getByRole("button", { name: /Eliminar activo/i }),
|
||||||
|
).toHaveCount(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -408,6 +408,110 @@ describe("i18n dictionaries", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("provides localized inventory asset UI copy for English and Spanish", () => {
|
||||||
|
expect(getDictionary("en").inventory.assets).toEqual({
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(getDictionary("es").inventory.assets).toEqual({
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it("keeps dashboard home dictionary keys aligned across locales", () => {
|
it("keeps dashboard home dictionary keys aligned across locales", () => {
|
||||||
expect(getDictionary("en").dashboardHome).toEqual({
|
expect(getDictionary("en").dashboardHome).toEqual({
|
||||||
heading: "Dashboard",
|
heading: "Dashboard",
|
||||||
|
|||||||
Reference in New Issue
Block a user