refactor(ui): modernize proposal wizard, dashboard cards and daily control calendar
Frontend-only application of prod_v2 (7bc6845, a29eafc). Dev docker-compose.yml change (b11cb36) and backups churn excluded.
This commit is contained in:
@@ -8,19 +8,17 @@ export interface TermsheetItem {
|
||||
}
|
||||
|
||||
export interface TermsheetCardProps {
|
||||
index: number; // 1-based → «گزینه N»
|
||||
index: number;
|
||||
milestones: Milestone[];
|
||||
onMilestonesChange: (ms: Milestone[]) => void;
|
||||
// اقلام قابل تیک (مرحله ۲)
|
||||
items?: TermsheetItem[];
|
||||
coveredIds?: string[]; // tempIdهایی که تیک دارند
|
||||
coveredIds?: string[];
|
||||
onCoverageChange?: (ids: string[]) => void;
|
||||
onRemove?: () => void;
|
||||
defaultAll?: boolean; // وقتی هیچ تیکی نیست، پیشفرض همه تیک بخورند
|
||||
defaultAll?: boolean;
|
||||
removable?: boolean;
|
||||
}
|
||||
|
||||
// کارت ترمشیت — هم در ویزارد (مرحله ۳) هم در مودال پذیرش/ویرایش
|
||||
export function TermsheetCard({
|
||||
index,
|
||||
milestones,
|
||||
@@ -33,7 +31,6 @@ export function TermsheetCard({
|
||||
removable = true,
|
||||
}: TermsheetCardProps) {
|
||||
const covered = new Set(coveredIds);
|
||||
// پیشفرض: وقتی هیچ قلمی تیک نخورده و defaultAll فعال است → همه تیک
|
||||
const effectiveCovered = covered.size === 0 && defaultAll ? items.map((i) => i.tempId) : coveredIds;
|
||||
|
||||
const toggle = (tempId: string, checked: boolean) => {
|
||||
@@ -49,17 +46,25 @@ export function TermsheetCard({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="variant-card">
|
||||
<div className="variant-head">
|
||||
<span className="variant-num">گزینه {faNum(index)}</span>
|
||||
<div className="rounded-xl border border-line bg-panel p-4">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="font-bold text-[13px]">گزینه {faNum(index)}</span>
|
||||
{removable && onRemove && (
|
||||
<button type="button" className="variant-x" title="حذف" onClick={onRemove}>
|
||||
×
|
||||
<button
|
||||
type="button"
|
||||
className="text-gray-400 hover:text-red-500 p-1 rounded transition"
|
||||
title="حذف گزینه"
|
||||
onClick={onRemove}
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.8} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mb-1.5 text-[12px] text-muted">مراحل پرداخت:</div>
|
||||
<div className="mb-1 flex flex-col gap-1.5">
|
||||
<div className="mb-2 flex flex-col gap-1.5">
|
||||
{milestones.map((m, i) => (
|
||||
<MilestoneRow
|
||||
key={i}
|
||||
@@ -69,33 +74,33 @@ export function TermsheetCard({
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<button type="button" className="btn-line-file mt-1" onClick={addMilestone}>
|
||||
+ مرحله پرداخت
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="w-full py-1.5 border border-dashed border-gray-300 hover:border-blue-500 hover:text-blue-600 rounded-lg text-[12px] text-gray-500 transition mb-3"
|
||||
onClick={addMilestone}
|
||||
>
|
||||
+ مرحله پرداخت
|
||||
</button>
|
||||
|
||||
{items.length > 0 && (
|
||||
<div className="ts-items-box">
|
||||
<div className="ts-items-title">این termsheet کدام اقلام (مرحله ۲) را پوشش میدهد؟</div>
|
||||
{items.length === 0 ? (
|
||||
<div className="ts-items-empty">هنوز قلمی در مرحلهی ۲ اضافه نشده.</div>
|
||||
) : (
|
||||
<div>
|
||||
{items.map((it) => (
|
||||
<label key={it.tempId} className="ts-item-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="ts-item-cb"
|
||||
data-item-temp-id={it.tempId}
|
||||
checked={effectiveCovered.includes(it.tempId)}
|
||||
onChange={(e) => toggle(it.tempId, e.target.checked)}
|
||||
/>
|
||||
{it.label}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div className="pt-2 border-t border-line/60">
|
||||
<div className="text-[12px] text-muted mb-1.5">این termsheet کدام اقلام (مرحله ۲) را پوشش میدهد؟</div>
|
||||
<div className="flex flex-col gap-1 text-[12px]">
|
||||
{items.map((it) => (
|
||||
<label key={it.tempId} className="flex items-center gap-2 cursor-pointer text-gray-700">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="rounded border-gray-300"
|
||||
checked={effectiveCovered.includes(it.tempId)}
|
||||
onChange={(e) => toggle(it.tempId, e.target.checked)}
|
||||
/>
|
||||
<span>{it.label}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -26,15 +26,21 @@ export function TopicGrid({ topics, selectedTopic, onSelect, showCounts = false,
|
||||
<button
|
||||
type="button"
|
||||
key={t.key}
|
||||
className={cn("topic-card", selected && "selected")}
|
||||
className={cn("topic-card flex flex-col items-center justify-center p-4", selected && "selected")}
|
||||
onClick={() => onSelect(t.name)}
|
||||
>
|
||||
<img src={t.icon} alt="" />
|
||||
<div className="t-name">{t.name}</div>
|
||||
{showCounts && <div className="t-count">{faNum(count)} تأمینکننده</div>}
|
||||
{/* استفاده از استایل اینلاین برای تضمین تغییر سایز و دور زدن CSSهای قبلی */}
|
||||
<img
|
||||
src={t.icon}
|
||||
alt=""
|
||||
style={{ width: '130px', height: '80px', objectFit: 'contain' }}
|
||||
className="mb-2"
|
||||
/>
|
||||
<div className="t-name text-[13px] font-semibold">{t.name}</div>
|
||||
{showCounts && <div className="t-count text-[11px] text-muted">{faNum(count)} تأمینکننده</div>}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user