TorbatYar/frontend/modules/commercial/components/BundleCard.tsx

127 lines
5.5 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import type { CommercialBundle, CommercialPricingItem, CommercialProduct } from "../types";
import { Badge, Button } from "@/components/ui";
import { enumLabel, formatMoney, humanizeCommercialCode } from "../presentation";
export function BundleCard({
bundle,
onSelect,
selected,
recommendationScore,
pricingItems = [],
products = [],
}: {
bundle: CommercialBundle;
onSelect?: () => void;
selected?: boolean;
recommendationScore?: number;
pricingItems?: CommercialPricingItem[];
products?: CommercialProduct[];
}) {
const features = bundle.feature_refs?.length
? bundle.feature_refs
: bundle.capability_codes || [];
const prices = (bundle.pricing_refs || [])
.map((ref) => pricingItems.find((item) => item.pricing_item_code === ref))
.filter((item): item is CommercialPricingItem => Boolean(item));
const productNames = (bundle.product_codes || []).map(
(code) => products.find((product) => product.product_code === code)?.display_name || humanizeCommercialCode(code)
);
const badge = bundle.recommendation_badge
? enumLabel(bundle.recommendation_badge)
: recommendationScore != null
? "پیشنهاد ویژه برای شما"
: null;
const unresolvedPriceLabels = (bundle.pricing_refs || [])
.filter((ref) => !prices.some((item) => item.pricing_item_code === ref))
.map((ref) => {
if (/monthly/i.test(ref)) return "پرداخت ماهانه";
if (/yearly|annual/i.test(ref)) return "پرداخت سالانه";
if (/trial/i.test(ref)) return "آزمایش رایگان";
if (/enterprise|quote/i.test(ref)) return "استعلام قیمت سازمانی";
return "روش پرداخت قابل انتخاب";
});
const hasCoupon = bundle.discount_refs?.some((ref) => /coupon/i.test(ref));
return (
<div
className={`rounded-2xl border p-5 ${
selected
? "border-primary bg-[var(--color-primary-soft)] shadow-md"
: "border-gray-200 bg-white shadow-sm"
}`}
>
<div className="flex flex-wrap items-start justify-between gap-2">
<div>
<h3 className="text-lg font-bold text-secondary">{bundle.display_name}</h3>
{bundle.description ? (
<p className="mt-1 text-sm text-gray-600">{bundle.description}</p>
) : null}
</div>
<div className="flex flex-wrap gap-1">
{badge ? (
<Badge variant="primary">{badge}</Badge>
) : null}
{bundle.trial_eligible ? <Badge variant="default">آزمایش رایگان</Badge> : null}
{typeof recommendationScore === "number" ? (
<Badge variant="default">متناسب با نیاز شما</Badge>
) : null}
</div>
</div>
{productNames.length ? (
<div className="mt-4">
<p className="text-xs font-semibold text-gray-500">محصولات این بسته</p>
<div className="mt-2 flex flex-wrap gap-2">
{productNames.map((name) => (
<span key={name} className="rounded-full bg-sky-50 px-3 py-1 text-xs text-sky-800">
{name}
</span>
))}
</div>
</div>
) : null}
{features.length ? (
<div className="mt-4">
<p className="text-xs font-semibold text-gray-500">امکانات کلیدی</p>
<div className="mt-2 flex flex-wrap gap-2">
{features.slice(0, 12).map((feature) => (
<span key={feature} className="rounded-full bg-emerald-50 px-3 py-1 text-xs text-emerald-800">
{humanizeCommercialCode(feature)}
</span>
))}
</div>
</div>
) : null}
{prices.length ? (
<div className="mt-4 grid gap-2 sm:grid-cols-2">
{prices.map((price) => (
<div key={price.pricing_item_code} className="rounded-xl bg-gray-50 p-3">
<p className="text-xs text-gray-500">{enumLabel(price.pricing_model)}</p>
<p className="mt-1 font-bold text-secondary">{formatMoney(price.amount_minor, price.currency)}</p>
{price.trial_days ? <p className="mt-1 text-xs text-primary">{price.trial_days} روز آزمایش رایگان</p> : null}
{price.tax_class_code ? <p className="mt-1 text-[11px] text-gray-500">مالیات مطابق قوانین محاسبه میشود</p> : null}
</div>
))}
</div>
) : null}
{unresolvedPriceLabels.length ? <div className="mt-4 flex flex-wrap gap-2">{Array.from(new Set(unresolvedPriceLabels)).map((label) => <span key={label} className="rounded-full bg-gray-100 px-3 py-1 text-xs text-gray-700">{label}</span>)}</div> : null}
{bundle.discount_refs?.length ? (
<div className="mt-3 flex flex-wrap gap-2 text-xs font-medium text-emerald-700">
<span className="rounded-full bg-emerald-50 px-3 py-1">تخفیف ویژه فعال</span>
{hasCoupon ? <span className="rounded-full bg-emerald-50 px-3 py-1">کوپن قابل استفاده</span> : null}
</div>
) : null}
{bundle.upgrade_path_code ? (
<p className="mt-1 text-xs text-primary">امکان ارتقا در هر زمان</p>
) : null}
{onSelect ? (
<Button className="mt-4 w-full" size="sm" variant={selected ? "primary" : "outline"} onClick={onSelect}>
{selected ? "این بسته انتخاب شد" : "انتخاب این بسته"}
</Button>
) : null}
</div>
);
}