Update UI and logic for supplier name and call button
This commit is contained in:
@@ -458,10 +458,16 @@ function adaptBrandLink(raw: RawBrandLink): BrandLinkRow {
|
||||
return { id: raw.id, brand_id: raw.brand_id, brand_name: raw.brand?.name || "" };
|
||||
}
|
||||
|
||||
<<<<<<< Updated upstream
|
||||
// تأمینکنندگان متصل به یک متریال — پاسخ GET /materials/{id}/suppliers
|
||||
// Raw shape mirrors the backend dict exactly: clean supplier objects produced
|
||||
// by an atomic JOIN (select(Supplier).join(SupplierMaterial)…) — the name
|
||||
// always comes from the suppliers table, never from the material row.
|
||||
=======
|
||||
// ── تأمینکنندههای متریال — پاسخ GET /materials/{id}/suppliers ──────────────
|
||||
// Raw shape mirrors the backend dict exactly. The backend uses an atomic
|
||||
// column-level JOIN so `name` always comes from the suppliers table.
|
||||
>>>>>>> Stashed changes
|
||||
interface RawMaterialSupplier {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -482,8 +488,11 @@ export interface MaterialSupplier {
|
||||
function adaptMaterialSupplier(raw: RawMaterialSupplier): MaterialSupplier {
|
||||
return {
|
||||
id: raw.id,
|
||||
<<<<<<< Updated upstream
|
||||
// Bound straight from the supplier object in the API response — there is
|
||||
// no fallback, so a supplier row can never render the material's name.
|
||||
=======
|
||||
>>>>>>> Stashed changes
|
||||
name: raw.name,
|
||||
phone: raw.phone,
|
||||
contact_person: raw.contact_person,
|
||||
@@ -1159,6 +1168,7 @@ export const api = {
|
||||
updateCanonicalUnit: async (id: string, canonical_unit_id: number): Promise<void> => {
|
||||
await patchData(`/materials/${id}`, { canonical_unit_id });
|
||||
},
|
||||
<<<<<<< Updated upstream
|
||||
// فهرست تأمینکنندههای متصل به یک متریال (GET /materials/{id}/suppliers)
|
||||
listSuppliers: async (materialId: string): Promise<MaterialSupplier[]> => {
|
||||
// The backend returns { data: [...clean supplier objects] } from an
|
||||
@@ -1166,6 +1176,12 @@ export const api = {
|
||||
// an empty array means the material genuinely has no suppliers.
|
||||
const raw = await getData<RawMaterialSupplier[]>(
|
||||
`/materials/${materialId}/suppliers`,
|
||||
=======
|
||||
// فهرست تأمینکنندههای متصل به یک متریال — GET /materials/{id}/suppliers
|
||||
listSuppliers: async (materialId: string): Promise<MaterialSupplier[]> => {
|
||||
const raw = await getData<RawMaterialSupplier[]>(
|
||||
`/materials/${materialId}/suppliers?page_size=100`,
|
||||
>>>>>>> Stashed changes
|
||||
);
|
||||
return raw.map(adaptMaterialSupplier);
|
||||
},
|
||||
|
||||
@@ -110,7 +110,7 @@ export function DashboardPage() {
|
||||
className="inline-flex items-center gap-1.5 px-3.5 py-2 rounded-xl border border-gray-200 dark:border-zinc-700 bg-white dark:bg-zinc-800 text-gray-700 dark:text-zinc-200 text-xs font-semibold hover:bg-gray-50 dark:hover:bg-zinc-700 shadow-2xs transition-all no-underline"
|
||||
>
|
||||
<Ruler className="h-4 w-4 text-gray-500 dark:text-zinc-400" />
|
||||
<span>ضرایب تبدیل</span>
|
||||
<span>اقلام و ضرایب</span>
|
||||
</Link>
|
||||
|
||||
{/* دکمه اصلی: افزودن پیشنهاد جدید */}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
Building2,
|
||||
@@ -103,9 +104,16 @@ export function UnitsPage() {
|
||||
const [rowsLoading, setRowsLoading] = useState(false);
|
||||
const [brandLinks, setBrandLinks] = useState<BrandLinkRow[]>([]);
|
||||
|
||||
<<<<<<< Updated upstream
|
||||
// تأمینکنندههای متریال انتخابشده — type imported from api.ts
|
||||
const [materialSuppliers, setMaterialSuppliers] = useState<MaterialSupplier[]>([]);
|
||||
const [suppliersLoading, setSuppliersLoading] = useState(false);
|
||||
=======
|
||||
// تأمینکنندههای متریال انتخابشده — داده از GET /materials/{id}/suppliers
|
||||
const [materialSuppliers, setMaterialSuppliers] = useState<MaterialSupplier[]>([]);
|
||||
const [suppliersLoading, setSuppliersLoading] = useState(false);
|
||||
// کلید پیشگیری از flicker: قبل از رسیدن پاسخ جدید، empty state نشان نمیدهیم
|
||||
>>>>>>> Stashed changes
|
||||
const [suppliersLoadedId, setSuppliersLoadedId] = useState<string | null>(null);
|
||||
|
||||
// واحدها و برندهای سراسری (فهرست دیتابیس)
|
||||
@@ -281,7 +289,11 @@ export function UnitsPage() {
|
||||
setSuppliersLoading(true);
|
||||
api.materials
|
||||
.listSuppliers(materialId)
|
||||
<<<<<<< Updated upstream
|
||||
.then((list) => {
|
||||
=======
|
||||
.then((list) => {
|
||||
>>>>>>> Stashed changes
|
||||
if (alive) {
|
||||
setMaterialSuppliers(list);
|
||||
setSuppliersLoadedId(materialId);
|
||||
@@ -291,9 +303,13 @@ export function UnitsPage() {
|
||||
if (!alive) return;
|
||||
setMaterialSuppliers([]);
|
||||
setSuppliersLoadedId(materialId);
|
||||
<<<<<<< Updated upstream
|
||||
// Surface the real error so backend issues are not silently hidden
|
||||
const msg = err instanceof Error ? err.message : "خواندن تأمینکنندههای متریال ناموفق بود";
|
||||
toast.error(msg);
|
||||
=======
|
||||
toast.error(err instanceof Error ? err.message : "خواندن تأمینکنندههای متریال ناموفق بود");
|
||||
>>>>>>> Stashed changes
|
||||
})
|
||||
.finally(() => { if (alive) setSuppliersLoading(false); });
|
||||
return () => { alive = false; };
|
||||
@@ -485,7 +501,7 @@ export function UnitsPage() {
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
<Header
|
||||
title="ضرایب تبدیل"
|
||||
title="اقلام و ضرایب"
|
||||
sub="متریال را انتخاب کنید — واحد مرجع و ضریب تبدیل همان متریال"
|
||||
backTo="/"
|
||||
backLabel="بازگشت به دفتر"
|
||||
@@ -582,6 +598,7 @@ export function UnitsPage() {
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<<<<<<< Updated upstream
|
||||
{/* تامینکنندگان متریال — پاسخ GET /materials/{id}/suppliers */}
|
||||
<div className="mb-6 flex flex-col overflow-hidden rounded-xl border border-line bg-panel shadow-sm">
|
||||
<div className="shrink-0 border-b border-line bg-panel px-4 py-3">
|
||||
@@ -617,6 +634,72 @@ export function UnitsPage() {
|
||||
{s.contact_person && <span>{s.contact_person}</span>}
|
||||
</div>
|
||||
)}
|
||||
=======
|
||||
{/* ── تامینکنندگان متریال ── */}
|
||||
<div className="mb-5 overflow-hidden rounded-xl border border-line bg-panel shadow-sm">
|
||||
{/* سرصفحه — همیشه نمایش داده میشود */}
|
||||
<div className="border-b border-line px-4 py-3">
|
||||
<h4 className="text-[14px] font-bold text-gray-900">تامینکنندگان متریال</h4>
|
||||
</div>
|
||||
|
||||
{/* محتوا — min-h برای جلوگیری از layout shift */}
|
||||
<div className="min-h-[80px] p-4 transition-opacity duration-300 ease-in-out">
|
||||
{suppliersLoading || materialId !== suppliersLoadedId ? (
|
||||
/* اسپینر — نه empty state — تا پاسخ API برسد */
|
||||
<div className="flex h-[80px] items-center justify-center">
|
||||
<span className="h-5 w-5 animate-spin rounded-full border-2 border-muted border-t-transparent" />
|
||||
</div>
|
||||
) : materialSuppliers.length === 0 ? (
|
||||
<p className="flex h-[80px] items-center justify-center text-sm text-muted">
|
||||
تامینکنندهای برای این متریال ثبت نشده است
|
||||
</p>
|
||||
) : (
|
||||
<div className="flex flex-col gap-3 mt-4 max-h-[320px] overflow-y-auto">
|
||||
{materialSuppliers.map((s) => (
|
||||
<div
|
||||
key={s.id}
|
||||
className="flex flex-col md:flex-row md:items-center justify-between p-4 bg-white border border-gray-200 rounded-xl shadow-sm gap-4 w-full"
|
||||
>
|
||||
{/* نام تامینکننده */}
|
||||
<div className="flex-1 flex flex-col gap-1 text-right">
|
||||
<span className="text-xs font-medium text-gray-900">نام تامینکننده</span>
|
||||
<Link
|
||||
to={`/suppliers/${s.id}`}
|
||||
className="font-medium text-accent hover:underline decoration-dashed"
|
||||
>
|
||||
{s.name}
|
||||
</Link>
|
||||
</div>
|
||||
{/* نام مسئول */}
|
||||
<div className="flex-1 flex flex-col gap-1 text-right">
|
||||
<span className="text-xs font-medium text-gray-900">نام مسئول</span>
|
||||
<span className="text-gray-500">
|
||||
{s.contact_person || <span className="text-gray-500">ثبت نشده</span>}
|
||||
</span>
|
||||
</div>
|
||||
{/* تلفن */}
|
||||
<div className="flex-1 flex flex-col gap-1 text-right">
|
||||
<span className="text-xs font-medium text-gray-900">تلفن</span>
|
||||
{s.phone ? (
|
||||
<a
|
||||
href={`tel:${s.phone}`}
|
||||
dir="ltr"
|
||||
className="font-medium text-accent hover:underline decoration-dashed"
|
||||
>
|
||||
{s.phone}
|
||||
</a>
|
||||
) : (
|
||||
<span className="text-gray-500">ثبت نشده</span>
|
||||
)}
|
||||
</div>
|
||||
{/* محل */}
|
||||
<div className="flex-1 flex flex-col gap-1 text-right">
|
||||
<span className="text-xs font-medium text-gray-900">محل</span>
|
||||
<span className="text-gray-500">
|
||||
{s.location || <span className="text-gray-500">ثبت نشده</span>}
|
||||
</span>
|
||||
</div>
|
||||
>>>>>>> Stashed changes
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user