مصالح و تجهیزات مصرفی
افزودن مصالح خارج برنامه
{consumedMaterials.length === 0 ? (
مصالح یا تجهیزاتی برای این وظیفه تعیین نشده است. میتوانید با دکمه بالا مصالح جدید اضافه کنید.
) : (
{consumedMaterials.map((item, idx) => {
const alloc = item.allocated_amount || 0;
const prevC = item.previous_consumed || 0;
const currentC = item.amount || 0;
const totC = prevC + currentC;
const rem = Math.max(0, alloc - totC);
const isOver = alloc > 0 && totC > alloc;
const overAmt = isOver ? totC - alloc : 0;
const pctConsumed = alloc > 0 ? Math.min(100, Math.round((totC / alloc) * 100)) : 100;
const specs =
task?.materials?.find(
(m) => m.name.trim().toLowerCase() === item.name.trim().toLowerCase()
)?.specifications || [];
return (
{alloc === 0 ? (
) : (
{item.name}
{item.unit && ({item.unit}) }
)}
{specs.length > 0 && (
{specs.map((s, si) => (
{s}
))}
)}
{alloc === 0 ? (
خارج از برنامه
handleRemoveMaterialRow(idx)}
className="text-slate-400 hover:text-rose-600 p-1 rounded-md hover:bg-rose-50 transition-colors"
title="حذف ردیف"
>
) : (
{isOver ? "⚠️ مازاد بر سقف" : "مجاز"}
)}
سقف مصوب
{alloc > 0 ? (
<>
{alloc} {" "}
{item.unit}
>
) : (
بدون سقف
)}
مصرف تاکنون
{prevC} {" "}
{item.unit}
0 ? "text-emerald-700" : "text-sky-700"
}`}
>
{isOver ? "مازاد سقف" : alloc > 0 ? "مانده مجاز" : "کل مصرفی"}
0 ? "text-emerald-700" : "text-sky-700"
}`}
>
{isOver ? (
+{overAmt} {item.unit}
) : alloc > 0 ? (
{rem} {item.unit}
) : (
{totC} {item.unit}
)}
{currentC > 0 && (
مجموع پس از ثبت:
{totC} {item.unit}
)}
{alloc > 0 && (
کل مصرف: {totC} از {alloc} {item.unit}
{totC > alloc ? `اضافه مصرف (${Math.round((totC / alloc) * 100)}٪)` : `${pctConsumed}٪ از سقف`}
80 ? "bg-amber-500" : "bg-emerald-500"
}`}
/>
)}
{isOver && (
میزان مصرف ({totC} {item.unit}) از سقف مصوب ({alloc} {item.unit}) بیشتر است. (+{overAmt} {item.unit} مازاد)
)}
);
})}
)}
{/* نیروی انسانی */}
نیروی انسانی تحویلشده و حاضر در کارگاه
setConsumedHr([...consumedHr, { role: "", planned_count: undefined, actual_count: 1, note: "" }])
}
className="text-[10px] font-semibold text-sky-700 hover:text-sky-900 bg-white border border-sky-200 rounded-lg px-2.5 py-1 flex items-center gap-1 shadow-2xs hover:bg-sky-50 transition-colors"
>
افزودن ردیف نیرو
{consumedHr.length === 0 ? (
ردیفی برای نیروی انسانی ثبت نشده است
) : (
consumedHr.map((item, idx) => (
{item.planned_count != null && item.planned_count !== item.actual_count && (
⚠️ مغایرت تأمین نیرو: {item.planned_count} نفر درخواستی سرپرست، ولی {item.actual_count} نفر تأمین شده است.
)}
))
)}
توضیحات و گزارش روزانه کارگاه (اختیاری)
(m.amount || 0) > 0) &&
!progressNote.trim())
}
onClick={() =>
progressMutation.mutate({
percentage: progress,
human_resources_used: consumedHr,
materials_used: consumedMaterials,
description: progressNote,
})
}
className={`${btnPrimary} mt-3 w-full`}
>
{progressMutation.isPending ? "در حال ثبت پیشرفت و گزارش مصرف..." : "ذخیره پیشرفت و ثبت مصرف منابع"}
{task.sync?.last_error && (
خطای همگامسازی: {task.sync.last_error}
)}
{/* کارت تاریخچه ثبت پیشرفت */}
{task.progress_logs && task.progress_logs.length > 0 && (
{task.progress_logs.map((log) => (
{log.progress_before}٪ ➔ {log.progress_after}٪
{log.progress_after - log.progress_before > 0 ? `+${log.progress_after - log.progress_before}٪ پیشرفت` : "ثبت پیشرفت"}
{relativeTime(log.created_at)}
{log.logged_by && توسط {log.logged_by} }
{log.human_resources_used && log.human_resources_used.length > 0 && (
نیروی کار حاضر:
{log.human_resources_used.map((hr, i) => (
{hr.role}:
{hr.actual_count} نفر حاضر
{hr.planned_count != null && hr.planned_count !== hr.actual_count && (
(درخواستی: {hr.planned_count})
)}
))}
)}
{log.materials_used && log.materials_used.length > 0 && (
مصالح مصرفشده:
{log.materials_used.map((mat, i) => (
{mat.name}:
{mat.amount} {mat.unit || "واحد"}
{mat.is_over_allocated && (
مازاد (+{mat.over_allocated_amount || mat.amount})
)}
))}
)}
{log.description && (
{log.description}
)}
))}
)}
{/* کارت تاریخهای وظیفه */}
تاریخهای برنامهریزیشده:
{canEditDates ? (
<>
تاریخ شروع برنامهریزیشده
تاریخ پایان برنامهریزیشده
{isDateRangeInvalid && (
تاریخ پایان نمیتواند کمتر از تاریخ شروع باشد
)}
datesMutation.mutate()}
className={`${btnSecondary} mt-2.5 w-full`}
>
{datesMutation.isPending ? "در حال ثبت..." : "ذخیره تاریخهای برنامهریزیشده"}
>
) : (
شروع:
{task.planned_start_jalali || task.start_date_jalali || (task.start_date ? formatDate(task.start_date) : "—")}
پایان:
{task.planned_end_jalali || task.due_date_jalali || (task.due_date ? formatDate(task.due_date) : "—")}
)}
تاریخهای واقعی (محاسبه و ثبت خودکار):
شروع واقعی:
{(task as any).actual_start_jalali || ((task as any).actual_start ? formatDate((task as any).actual_start) : "شروع نشده")}
{!(task as any).actual_start && (
با افزایش پیشرفت ثبت میشود
)}
پایان واقعی:
{(task as any).actual_end_jalali || ((task as any).actual_end ? formatDate((task as any).actual_end) : "تکمیل نشده")}
{!(task as any).actual_end && (
در پیشرفت ۱۰۰٪ ثبت میشود
)}
{/* کارت کنترل کیفیت و بازرسیها */}
{/* کارت ارتباطات */}
ارتباطات و روابط پیشنیازی (FS, SS, FF, SF)
{canEditTasks && (
setShowAddRelationModal(true)}
className="inline-flex items-center gap-1 rounded-lg bg-sky-50 px-2 py-1 text-[11px] font-semibold text-sky-700 hover:bg-sky-100 transition-colors"
>
افزودن وابستگی
)}
}
>
{(task.relations?.blocked_by || []).map((r) => (
{r.relation_type || "FS"}
{r.subject || "وظیفه پیشنیاز"}
{Boolean(r.lag_days) && (
(تاخیر: {(r.lag_days ?? 0) > 0 ? `+${r.lag_days}` : r.lag_days} روز)
)}
{canEditTasks && r.id && (
r.id && deleteRelationMutation.mutate(r.id)}
disabled={deleteRelationMutation.isPending}
className="text-slate-400 hover:text-red-500 p-1 transition-colors"
title="حذف رابطه"
>
)}
))}
)}
{(task.relations?.blocks || []).map((r) => (
{r.relation_type || "FS"}
{r.subject || "وظیفه پسنیاز"}
{Boolean(r.lag_days) && (
(تاخیر: {(r.lag_days ?? 0) > 0 ? `+${r.lag_days}` : r.lag_days} روز)
)}
{canEditTasks && r.id && (
r.id && deleteRelationMutation.mutate(r.id)}
disabled={deleteRelationMutation.isPending}
className="text-slate-400 hover:text-red-500 p-1 transition-colors"
title="حذف رابطه"
>
)}
))}
)}
{/* کارت زیرتسکها */}
{task.children && task.children.length > 0 && (