fix(ui): align shared board plumbing with ky v2 and react-router

- api.ts: use ky v2 hook object args, prefix (not prefixUrl), and normalizeApiRequestError
- replace react-router-dom with react-router in layout, topbar, and vendor-pos view
- move MUI style props (justifyContent, alignItems, flexWrap, display, fontWeight) into sx
- resource-list-table/query-key: use local types instead of missing work-order-list module
This commit is contained in:
Arthur Bassi 2026-07-17 13:31:56 -03:00
parent 01939cbb70
commit a3e8a533b1
10 changed files with 32 additions and 35 deletions

View file

@ -1,8 +1,8 @@
import ky, { type HTTPError, type KyInstance, type Options } from "ky"; import ky, { type KyInstance, type Options } from "ky";
import { env } from "@/lib/env"; import { env } from "@/lib/env";
import { resolveApiPrefixUrl } from "@/lib/resolve-api-prefix-url"; import { resolveApiPrefixUrl } from "@/lib/resolve-api-prefix-url";
import { API_PATHS } from "@/api/api-paths"; import { API_PATHS } from "@/api/api-paths";
import { createApiErrorFromResponse, mapHttpStatusToMessage } from "@/api/api-error"; import { mapHttpStatusToMessage, normalizeApiRequestError } from "@/api/api-error";
import { queryKeys } from "@/infra/query-key/query-key"; import { queryKeys } from "@/infra/query-key/query-key";
import queryClient from "@/lib/query/query-client"; import queryClient from "@/lib/query/query-client";
import { isAuthBypassEnabled } from "@/lib/auth/auth-bypass"; import { isAuthBypassEnabled } from "@/lib/auth/auth-bypass";
@ -22,36 +22,23 @@ function isAuthLoginRequest(request: Request): boolean {
} }
export const api: KyInstance = ky.create({ export const api: KyInstance = ky.create({
prefixUrl: resolveApiPrefixUrl(env.apiUrl), prefix: resolveApiPrefixUrl(env.apiUrl),
headers: { headers: {
Accept: "application/json", Accept: "application/json",
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
hooks: { hooks: {
beforeRequest: [ beforeRequest: [
(request) => { ({ request }) => {
const token = getAccessToken(); const token = getAccessToken();
if (token) { if (token) {
request.headers.set("Authorization", token); request.headers.set("Authorization", token);
} }
}, },
], ],
beforeError: [ beforeError: [({ error }) => normalizeApiRequestError(error)],
async (error: HTTPError) => {
const { response } = error;
if (!response) {
error.message = "No response from server. Please check your connection.";
return error;
}
const apiError = await createApiErrorFromResponse(response);
error.message = apiError.message;
return error;
},
],
afterResponse: [ afterResponse: [
async (request, _options, response) => { ({ request, response }) => {
if (response.status !== 401 || isAuthLoginRequest(request) || isAuthBypassEnabled()) { if (response.status !== 401 || isAuthLoginRequest(request) || isAuthBypassEnabled()) {
return response; return response;
} }

View file

@ -1,5 +1,5 @@
import { useState } from "react"; import { useState } from "react";
import { Outlet } from "react-router-dom"; import { Outlet } from "react-router";
import { Box } from "@mui/material"; import { Box } from "@mui/material";
import { ProtectedRoute } from "@/components/auth/protected-route"; import { ProtectedRoute } from "@/components/auth/protected-route";

View file

@ -1,5 +1,5 @@
import { useState, type ReactNode } from "react"; import { useState, type ReactNode } from "react";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router";
import { import {
Alert, Alert,
Box, Box,
@ -33,7 +33,7 @@ function formatMoney(value: number | null | undefined): string {
function InfoRow({ label, children }: { label: string; children: ReactNode }) { function InfoRow({ label, children }: { label: string; children: ReactNode }) {
return ( return (
<Box> <Box>
<Typography variant="caption" color="text.secondary" display="block"> <Typography variant="caption" color="text.secondary" sx={{ display: "block" }}>
{label} {label}
</Typography> </Typography>
<Typography variant="body2"> <Typography variant="body2">
@ -107,7 +107,11 @@ export default function VendorPoViewPage() {
return ( return (
<Box className="mx-auto max-w-5xl p-4"> <Box className="mx-auto max-w-5xl p-4">
<Stack direction="row" justifyContent="space-between" alignItems="center" className="mb-4"> <Stack
direction="row"
className="mb-4"
sx={{ justifyContent: "space-between", alignItems: "center" }}
>
<Button variant="text" onClick={() => navigate(-1)}> <Button variant="text" onClick={() => navigate(-1)}>
← Back ← Back
</Button> </Button>
@ -118,7 +122,7 @@ export default function VendorPoViewPage() {
<Stack spacing={1} className="mb-4"> <Stack spacing={1} className="mb-4">
<Typography variant="h5">{dispatch.poNumber || "(no PO #)"}</Typography> <Typography variant="h5">{dispatch.poNumber || "(no PO #)"}</Typography>
<Stack direction="row" spacing={1} alignItems="center" flexWrap="wrap"> <Stack direction="row" spacing={1} sx={{ alignItems: "center", flexWrap: "wrap" }}>
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary">
Dispatch {dispatch.dispatchNumber || "—"} Dispatch {dispatch.dispatchNumber || "—"}
</Typography> </Typography>
@ -256,7 +260,10 @@ export default function VendorPoViewPage() {
<Stack spacing={2}> <Stack spacing={2}>
{dispatch.upliftRequests.map((u) => ( {dispatch.upliftRequests.map((u) => (
<Box key={String(u.id)} className="rounded border border-border p-3"> <Box key={String(u.id)} className="rounded border border-border p-3">
<Stack direction="row" justifyContent="space-between" alignItems="center"> <Stack
direction="row"
sx={{ justifyContent: "space-between", alignItems: "center" }}
>
<Typography variant="body2" className="font-semibold"> <Typography variant="body2" className="font-semibold">
{formatMoney(u.currentNTE)} → {formatMoney(u.requestedNTE)} {formatMoney(u.currentNTE)} → {formatMoney(u.requestedNTE)}
<Typography <Typography

View file

@ -38,7 +38,7 @@ export function CommentTimeline({
<Stack spacing={2} className={`${maxHeightClass} overflow-y-auto`}> <Stack spacing={2} className={`${maxHeightClass} overflow-y-auto`}>
{sorted.map((item) => ( {sorted.map((item) => (
<Box key={String(item.id)} className="rounded border border-border p-3"> <Box key={String(item.id)} className="rounded border border-border p-3">
<Stack direction="row" justifyContent="space-between" className="mb-1"> <Stack direction="row" className="mb-1" sx={{ justifyContent: "space-between" }}>
<Typography variant="caption" className="font-semibold"> <Typography variant="caption" className="font-semibold">
{item.author} {item.author}
</Typography> </Typography>

View file

@ -21,7 +21,7 @@ export function DetailField({ label, value, className, highlight }: DetailFieldP
return ( return (
<Box className={className}> <Box className={className}>
<Typography variant="caption" color="text.secondary" display="block"> <Typography variant="caption" color="text.secondary" sx={{ display: "block" }}>
{label} {label}
</Typography> </Typography>
<Typography variant="body2" className={colorClass}> <Typography variant="body2" className={colorClass}>

View file

@ -1,5 +1,5 @@
import { useState } from "react"; import { useState } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router";
import LogoutOutlinedIcon from "@mui/icons-material/LogoutOutlined"; import LogoutOutlinedIcon from "@mui/icons-material/LogoutOutlined";
import SettingsOutlinedIcon from "@mui/icons-material/SettingsOutlined"; import SettingsOutlinedIcon from "@mui/icons-material/SettingsOutlined";
import { import {
@ -199,7 +199,7 @@ export function AppTopbar({ onToggleNav }: AppTopbarProps) {
{userInitials} {userInitials}
</Box> </Box>
<Box> <Box>
<Typography variant="body2" fontWeight={600}> <Typography variant="body2" sx={{ fontWeight: 600 }}>
{user?.fullname || "User"} {user?.fullname || "User"}
</Typography> </Typography>
<Typography variant="caption" color="text.secondary"> <Typography variant="caption" color="text.secondary">

View file

@ -24,13 +24,15 @@ export function PageHeader({
return ( return (
<Stack <Stack
direction="row" direction="row"
alignItems="center"
justifyContent="space-between"
className={cn( className={cn(
surface && surface &&
"-mx-2.5 -mt-2.5 shrink-0 border-b border-(--color-border) bg-(--color-bg-surface) px-5 py-4", "-mx-2.5 -mt-2.5 shrink-0 border-b border-(--color-border) bg-(--color-bg-surface) px-5 py-4",
className, className,
)} )}
sx={{
alignItems: "center",
justifyContent: "space-between",
}}
> >
<Stack spacing={0}> <Stack spacing={0}>
{eyebrow && ( {eyebrow && (
@ -53,7 +55,7 @@ export function PageHeader({
)} )}
</Stack> </Stack>
{actions && ( {actions && (
<Stack direction="row" spacing={1} alignItems="center" flexWrap="nowrap"> <Stack direction="row" spacing={1} sx={{ alignItems: "center", flexWrap: "nowrap" }}>
{actions} {actions}
</Stack> </Stack>
)} )}

View file

@ -9,7 +9,8 @@ import {
TableRow, TableRow,
} from "@mui/material"; } from "@mui/material";
import { TableEmptyRow, TableLoadingRow } from "@/components/ui/table-state-rows"; import { TableEmptyRow, TableLoadingRow } from "@/components/ui/table-state-rows";
import type { SortDirection } from "@/domain/work-orders/types/work-order-list";
export type SortDirection = "asc" | "desc" | "";
export type ResourceListColumn<T> = { export type ResourceListColumn<T> = {
id: string; id: string;

View file

@ -16,7 +16,7 @@ export function StatusChipFilter({
return ( return (
<div> <div>
<span className="mb-1 block text-[11px] font-semibold text-muted-foreground">{label}</span> <span className="mb-1 block text-[11px] font-semibold text-muted-foreground">{label}</span>
<Stack direction="row" spacing={0.75} flexWrap="wrap" useFlexGap> <Stack direction="row" spacing={0.75} useFlexGap sx={{ flexWrap: "wrap" }}>
<Chip <Chip
label="All" label="All"
size="small" size="small"

View file

@ -1,4 +1,4 @@
import type { WorkOrdersListParams } from "@/domain/work-orders/types/work-order-list"; type WorkOrdersListParams = Record<string, unknown>;
export const queryKeys = { export const queryKeys = {
auth: { auth: {