268 lines
7.9 KiB
TypeScript
268 lines
7.9 KiB
TypeScript
|
|
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;
|