erp-platform/ui/src/views/mrp/components/MaterialRequirements.tsx
2025-09-15 12:31:47 +03:00

267 lines
7.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

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.

import React, { useState } from "react";
import {
FaCalendar,
FaBullseye, // Keep for icon consistency if needed elsewhere
} from "react-icons/fa";
import {
MrpMaterialRequirement,
RequirementSourceTypeEnum,
} from "../../../types/mrp";
import DataTable, { Column } from "../../../components/common/DataTable";
import {
getRequirementSourceTypeColor,
getRequirementSourceTypeText,
} from "../../../utils/erp";
interface MaterialRequirementsProps {
materialRequirements: MrpMaterialRequirement[];
}
const MaterialRequirements: React.FC<MaterialRequirementsProps> = ({
materialRequirements,
}) => {
const [searchTerm, setSearchTerm] = useState("");
const [selectedSource, setSelectedSource] = useState<
RequirementSourceTypeEnum | "all"
>("all");
const [sortBy, setSortBy] = useState<"date" | "quantity" | "priority">(
"date"
);
// Event handlers
const handleViewDetails = (requirement: MrpMaterialRequirement) => {
console.log("View requirement details:", requirement);
};
const filteredRequirements = materialRequirements
.filter((req) => {
if (
searchTerm &&
!req.material?.name?.toLowerCase().includes(searchTerm.toLowerCase()) &&
!req.material?.code?.toLowerCase().includes(searchTerm.toLowerCase())
) {
return false;
}
if (selectedSource !== "all" && req.sourceType !== selectedSource) {
return false;
}
return true;
})
.sort((a, b) => {
switch (sortBy) {
case "date":
return (
new Date(a.requirementDate).getTime() -
new Date(b.requirementDate).getTime()
);
case "quantity":
return b.netRequirement - a.netRequirement;
case "priority":
return b.grossRequirement - a.grossRequirement;
default:
return 0;
}
});
const getUrgencyLevel = (requirement: MrpMaterialRequirement) => {
const today = new Date();
const reqDate = new Date(requirement.requirementDate);
const daysUntilRequired = Math.ceil(
(reqDate.getTime() - today.getTime()) / (1000 * 60 * 60 * 24)
);
if (daysUntilRequired < 0)
return {
level: "overdue",
color: "bg-red-100 text-red-800",
label: "Gecikmiş",
};
if (daysUntilRequired <= 7)
return {
level: "urgent",
color: "bg-orange-100 text-orange-800",
label: "Acil",
};
if (daysUntilRequired <= 30)
return {
level: "soon",
color: "bg-yellow-100 text-yellow-800",
label: "Yakın",
};
return {
level: "normal",
color: "bg-green-100 text-green-800",
label: "Normal",
};
};
const requirementColumns: Column<MrpMaterialRequirement>[] = [
{
key: "material",
header: "Malzeme",
sortable: true,
render: (req: MrpMaterialRequirement) => (
<div>
<div className="font-medium text-gray-900">
{req.material?.name || `Material-${req.materialId.substring(0, 8)}`}
</div>
<div className="text-sm text-gray-500">{req.material?.code}</div>
</div>
),
},
{
key: "sourceType",
header: "Kaynak",
render: (req: MrpMaterialRequirement) => (
<span
className={`px-2 py-1 text-xs font-medium rounded-full ${getRequirementSourceTypeColor(
req.sourceType
)}`}
>
{getRequirementSourceTypeText(req.sourceType)}
</span>
),
},
{
key: "urgency",
header: "Aciliyet",
render: (req: MrpMaterialRequirement) => {
const urgency = getUrgencyLevel(req);
return (
<span
className={`px-2 py-1 text-xs font-medium rounded-full ${urgency.color}`}
>
{urgency.label}
</span>
);
},
},
{
key: "requirements",
header: "İhtiyaçlar",
render: (req: MrpMaterialRequirement) => (
<div className="text-sm">
<div className="flex justify-between">
<span>Brüt:</span>
<span className="font-medium">
{req.grossRequirement.toLocaleString()}
</span>
</div>
<div className="flex justify-between">
<span>Mevcut:</span>
<span className="font-medium">
{req.projectedAvailable.toLocaleString()}
</span>
</div>
<div className="flex justify-between border-t pt-1">
<span>Net:</span>
<span
className={`font-medium ${
req.netRequirement > 0 ? "text-red-600" : "text-green-600"
}`}
>
{req.netRequirement.toLocaleString()}
</span>
</div>
</div>
),
},
{
key: "scheduled",
header: "Planlanan",
render: (req: MrpMaterialRequirement) => (
<div className="text-sm">
<div>Gelen: {req.scheduledReceipts.toLocaleString()}</div>
<div>Sipariş: {req.plannedOrderReceipt.toLocaleString()}</div>
</div>
),
},
{
key: "dates",
header: "Tarihler",
render: (req: MrpMaterialRequirement) => (
<div className="text-sm">
<div className="flex items-center gap-1">
<FaCalendar className="w-3 h-3 text-gray-400" />
<span>
İhtiyaç:{" "}
{new Date(req.requirementDate).toLocaleDateString("tr-TR")}
</span>
</div>
<div className="flex items-center gap-1">
<FaBullseye className="w-3 h-3 text-gray-400" />
<span>
Plan:{" "}
{new Date(req.plannedReceiptDate).toLocaleDateString("tr-TR")}
</span>
</div>
</div>
),
},
{
key: "actions",
header: "İşlemler",
render: (req: MrpMaterialRequirement) => (
<button
onClick={() => handleViewDetails(req)}
className="px-3 py-1 text-sm bg-blue-50 text-blue-600 rounded hover:bg-blue-100 transition-colors"
>
Detay
</button>
),
},
];
return (
<>
{/* Filters */}
<div className="flex gap-2 items-center">
<div className="flex-1">
<input
type="text"
placeholder="Malzeme adı veya kodu ara..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full px-2 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<select
value={selectedSource}
onChange={(e) =>
setSelectedSource(
e.target.value as RequirementSourceTypeEnum | "all"
)
}
className="px-2 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="all">Tüm Kaynaklar</option>
{Object.values(RequirementSourceTypeEnum).map((source) => (
<option key={source} value={source}>
{getRequirementSourceTypeText(source)}
</option>
))}
</select>
<select
value={sortBy}
onChange={(e) =>
setSortBy(e.target.value as "date" | "quantity" | "priority")
}
className="px-2 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="date">Tarihe Göre</option>
<option value="quantity">Miktara Göre</option>
<option value="priority">Önceliğe Göre</option>
</select>
</div>
{/* Data Table */}
<div className="bg-white rounded-lg shadow-sm border">
<DataTable data={filteredRequirements} columns={requirementColumns} />
</div>
</>
);
};
export default MaterialRequirements;