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

428 lines
13 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 {
FaCheckCircle,
FaClock,
FaExclamationTriangle,
FaCalendar,
} from "react-icons/fa";
import {
RecommendationTypeEnum,
RecommendationStatusEnum,
MrpPurchaseSuggestion,
} from "../../../types/mrp";
import DataTable, { Column } from "../../../components/common/DataTable";
import { mockBusinessParties } from "../../../mocks/mockBusinessParties";
import { PriorityEnum } from "../../../types/common";
import {
getPriorityColor,
getPriorityText,
getRecommendationStatusColor,
getRecommendationStatusText,
} from "../../../utils/erp";
interface PurchaseSuggestionsProps {
purchaseSuggestions: MrpPurchaseSuggestion[];
}
const PurchaseSuggestions: React.FC<PurchaseSuggestionsProps> = ({
purchaseSuggestions,
}) => {
const [searchTerm, setSearchTerm] = useState("");
const [selectedStatus, setSelectedStatus] = useState<
RecommendationStatusEnum | "all"
>("all");
const [selectedPriority, setSelectedPriority] = useState<
PriorityEnum | "all"
>("all");
const [sortBy, setSortBy] = useState<
"date" | "cost" | "priority" | "leadTime"
>("date");
// Event handlers
const handleCreatePurchaseRequest = (suggestion: MrpPurchaseSuggestion) => {
console.log("Create purchase request:", suggestion);
};
const handleApprove = (id: string) => {
console.log("Approve suggestion:", id);
};
const handleReject = (id: string) => {
console.log("Reject suggestion:", id);
};
const handleViewDetails = (suggestion: MrpPurchaseSuggestion) => {
console.log("View suggestion details:", suggestion);
};
const filteredSuggestions = purchaseSuggestions
.filter((suggestion) => {
if (
searchTerm &&
!suggestion.material?.name
?.toLowerCase()
.includes(searchTerm.toLowerCase()) &&
!suggestion.recommendedAction
.toLowerCase()
.includes(searchTerm.toLowerCase())
) {
return false;
}
if (selectedStatus !== "all" && suggestion.status !== selectedStatus) {
return false;
}
if (
selectedPriority !== "all" &&
suggestion.priority !== selectedPriority
) {
return false;
}
return (
suggestion.recommendationType ===
RecommendationTypeEnum.PurchaseRequisition
);
})
.sort((a, b) => {
switch (sortBy) {
case "date":
return new Date(a.dueDate).getTime() - new Date(b.dueDate).getTime();
case "cost":
return b.estimatedCost - a.estimatedCost;
case "priority": {
const priorityOrder = { URGENT: 4, HIGH: 3, NORMAL: 2, LOW: 1 };
return (
(priorityOrder[b.priority as keyof typeof priorityOrder] || 0) -
(priorityOrder[a.priority as keyof typeof priorityOrder] || 0)
);
}
case "leadTime":
return a.leadTime - b.leadTime;
default:
return 0;
}
});
const getUrgencyLevel = (suggestion: MrpPurchaseSuggestion) => {
const today = new Date();
const dueDate = new Date(suggestion.dueDate);
const leadTimeDate = new Date(
today.getTime() + suggestion.leadTime * 24 * 60 * 60 * 1000
);
if (leadTimeDate > dueDate) {
return {
level: "critical",
color: "bg-red-100 text-red-800",
label: "Kritik",
icon: FaExclamationTriangle,
};
}
const daysUntilDue = Math.ceil(
(dueDate.getTime() - today.getTime()) / (1000 * 60 * 60 * 24)
);
if (daysUntilDue <= 7)
return {
level: "urgent",
color: "bg-orange-100 text-orange-800",
label: "Acil",
icon: FaClock,
};
if (daysUntilDue <= 30)
return {
level: "soon",
color: "bg-yellow-100 text-yellow-800",
label: "Yakın",
icon: FaCalendar,
};
return {
level: "normal",
color: "bg-green-100 text-green-800",
label: "Normal",
icon: FaCheckCircle,
};
};
const columns: Column<MrpPurchaseSuggestion>[] = [
{
key: "material",
header: "Malzeme",
sortable: true,
render: (suggestion: MrpPurchaseSuggestion) => (
<div>
<div className="font-medium text-gray-900">
{suggestion.material?.name ||
`Material-${suggestion.materialId.substring(0, 8)}`}
</div>
<div className="text-sm text-gray-500">
{suggestion.material?.code}
</div>
</div>
),
},
{
key: "urgency",
header: "Aciliyet",
render: (suggestion: MrpPurchaseSuggestion) => {
const urgency = getUrgencyLevel(suggestion);
const IconComponent = urgency.icon;
return (
<div className="flex items-center gap-1.5">
<span
className={`px-1.5 py-0.5 text-xs font-medium rounded-full ${urgency.color}`}
>
{urgency.label}
</span>
<IconComponent className="w-3.5 h-3.5 text-gray-400" />
</div>
);
},
},
{
key: "quantities",
header: "Miktarlar",
render: (suggestion: MrpPurchaseSuggestion) => (
<div className="text-sm">
<div className="flex justify-between">
<span>İhtiyaç:</span>
<span className="font-medium">
{suggestion.quantity.toLocaleString()}
</span>
</div>
<div className="flex justify-between">
<span>Önerilen:</span>
<span className="font-medium text-blue-600">
{suggestion.suggestedQuantity.toLocaleString()}
</span>
</div>
<div className="flex justify-between">
<span>EOQ:</span>
<span className="font-medium text-green-600">
{suggestion.economicOrderQuantity.toLocaleString()}
</span>
</div>
{suggestion.minimumOrderQuantity > 0 && (
<div className="flex justify-between text-xs text-gray-500">
<span>Min:</span>
<span>{suggestion.minimumOrderQuantity.toLocaleString()}</span>
</div>
)}
</div>
),
},
{
key: "supplier",
header: "Tedarikçi",
render: (suggestion: MrpPurchaseSuggestion) => (
<div>
{suggestion.supplier ? (
<div>
<div className="font-medium text-gray-900">
{
mockBusinessParties.find(
(a) => a.id === suggestion.supplier?.supplierId
)?.name
}
</div>
</div>
) : (
<span className="text-gray-400">Tedarikçi seçilmedi</span>
)}
</div>
),
},
{
key: "cost",
header: "Maliyet",
render: (suggestion: MrpPurchaseSuggestion) => (
<div className="text-right">
<div className="font-medium text-gray-900">
{suggestion.estimatedCost.toLocaleString()}
</div>
{suggestion.supplier && (
<div className="text-xs text-gray-500">
Birim: {suggestion.supplier.price.toLocaleString()}
</div>
)}
</div>
),
},
{
key: "dates",
header: "Tarihler",
render: (suggestion: MrpPurchaseSuggestion) => (
<div className="text-sm">
<div className="flex items-center gap-1">
<FaCalendar className="w-3 h-3 text-gray-400" />
<span>
Vade: {new Date(suggestion.dueDate).toLocaleDateString("tr-TR")}
</span>
</div>
<div className="flex items-center gap-1">
<FaClock className="w-3 h-3 text-gray-400" />
<span>Teslimat: {suggestion.leadTime} gün</span>
</div>
</div>
),
},
{
key: "priority",
header: "Öncelik",
render: (suggestion: MrpPurchaseSuggestion) => (
<span
className={`px-1.5 py-0.5 text-xs font-medium rounded-full ${getPriorityColor(
suggestion.priority
)}`}
>
{getPriorityText(suggestion.priority)}
</span>
),
},
{
key: "status",
header: "Durum",
render: (suggestion: MrpPurchaseSuggestion) => (
<span
className={`px-1.5 py-0.5 text-xs font-medium rounded-full ${getRecommendationStatusColor(
suggestion.status
)}`}
>
{getRecommendationStatusText(suggestion.status)}
</span>
),
},
{
key: "actions",
header: "İşlemler",
render: (suggestion: MrpPurchaseSuggestion) => (
<div className="flex gap-1">
{suggestion.status === RecommendationStatusEnum.Open && (
<>
<button
onClick={() => handleCreatePurchaseRequest(suggestion)}
className="px-1.5 py-0.5 text-xs bg-blue-50 text-blue-600 rounded hover:bg-blue-100 transition-colors"
title="Satın Alma Talebi Oluştur"
>
Talep Oluştur
</button>
<button
onClick={() => handleApprove(suggestion.id)}
className="px-1.5 py-0.5 text-xs bg-green-50 text-green-600 rounded hover:bg-green-100 transition-colors"
title="Onayla"
>
Onayla
</button>
<button
onClick={() => handleReject(suggestion.id)}
className="px-1.5 py-0.5 text-xs bg-red-50 text-red-600 rounded hover:bg-red-100 transition-colors"
title="Reddet"
>
Reddet
</button>
</>
)}
<button
onClick={() => handleViewDetails(suggestion)}
className="px-1.5 py-0.5 text-xs bg-gray-50 text-gray-600 rounded hover:bg-gray-100 transition-colors"
title="Detayları Görüntüle"
>
Detay
</button>
</div>
),
},
];
// Urgency distribution
const urgencyDistribution = [
{ level: "critical", count: 0, cost: 0 },
{ level: "urgent", count: 0, cost: 0 },
{ level: "soon", count: 0, cost: 0 },
{ level: "normal", count: 0, cost: 0 },
];
purchaseSuggestions
.filter((s) => s.status === RecommendationStatusEnum.Open)
.forEach((s) => {
const urgency = getUrgencyLevel(s);
const index = urgencyDistribution.findIndex(
(u) => u.level === urgency.level
);
if (index >= 0) {
urgencyDistribution[index].count++;
urgencyDistribution[index].cost += s.estimatedCost;
}
});
return (
<>
{/* Filters */}
<div className="flex gap-2 items-center text-sm">
<div className="flex-1">
<input
type="text"
placeholder="Malzeme adı, tedarikçi veya aksiyon ara..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full px-2 py-1.5 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<select
value={selectedStatus}
onChange={(e) =>
setSelectedStatus(
e.target.value as RecommendationStatusEnum | "all"
)
}
className="px-2 py-1.5 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="all">Tüm Durumlar</option>
{Object.values(RecommendationStatusEnum).map((status) => (
<option key={status} value={status}>
{getRecommendationStatusText(status)}
</option>
))}
</select>
<select
value={selectedPriority}
onChange={(e) =>
setSelectedPriority(e.target.value as PriorityEnum | "all")
}
className="px-2 py-1.5 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="all">Tüm Öncelikler</option>
{Object.values(PriorityEnum).map((priority) => (
<option key={priority} value={priority}>
{getPriorityText(priority)}
</option>
))}
</select>
<select
value={sortBy}
onChange={(e) =>
setSortBy(
e.target.value as "date" | "cost" | "priority" | "leadTime"
)
}
className="px-2 py-1.5 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="cost">Maliyete Göre</option>
<option value="priority">Önceliğe Göre</option>
<option value="leadTime">Teslimat Süresine Göre</option>
</select>
</div>
{/* Data Table */}
<div className="bg-white rounded-lg shadow-sm border">
<DataTable data={filteredSuggestions} columns={columns} />
</div>
</>
);
};
export default PurchaseSuggestions;