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

365 lines
12 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, useEffect } from "react";
import { FaTimes, FaSave } from "react-icons/fa";
import {
PsProjectRisk,
RiskCategoryEnum,
RiskProbabilityEnum,
RiskImpactEnum,
RiskLevelEnum,
RiskStatusEnum,
} from "../../../types/ps";
import {
getRiskCategoryText,
getRiskProbabilityText,
getRiskImpactText,
getRiskLevelColor,
getRiskLevelText,
getRiskStatusText,
} from "../../../utils/erp";
interface RiskModalProps {
isOpen: boolean;
onClose: () => void;
risk?: PsProjectRisk | null;
onSubmit: (riskData: Partial<PsProjectRisk>) => void;
mode: "create" | "edit";
}
const RiskModal: React.FC<RiskModalProps> = ({
isOpen,
onClose,
risk,
onSubmit,
mode,
}) => {
const [formData, setFormData] = useState({
title: "",
description: "",
category: RiskCategoryEnum.Technical,
probability: RiskProbabilityEnum.Medium,
impact: RiskImpactEnum.Medium,
riskLevel: RiskLevelEnum.Medium,
status: RiskStatusEnum.Identified,
mitigationPlan: "",
contingencyPlan: "",
ownerId: "",
reviewDate: "",
});
useEffect(() => {
if (mode === "edit" && risk) {
setFormData({
title: risk.title || "",
description: risk.description || "",
category: risk.category || RiskCategoryEnum.Technical,
probability: risk.probability || RiskProbabilityEnum.Medium,
impact: risk.impact || RiskImpactEnum.Medium,
riskLevel: risk.riskLevel || RiskLevelEnum.Medium,
status: risk.status || RiskStatusEnum.Identified,
mitigationPlan: risk.mitigationPlan || "",
contingencyPlan: risk.contingencyPlan || "",
ownerId: risk.ownerId || "",
reviewDate: risk.reviewDate
? new Date(risk.reviewDate).toISOString().split("T")[0]
: "",
});
} else if (mode === "create") {
setFormData({
title: "",
description: "",
category: RiskCategoryEnum.Technical,
probability: RiskProbabilityEnum.Medium,
impact: RiskImpactEnum.Medium,
riskLevel: RiskLevelEnum.Medium,
status: RiskStatusEnum.Identified,
mitigationPlan: "",
contingencyPlan: "",
ownerId: "",
reviewDate: "",
});
}
}, [mode, risk, isOpen]);
// Auto-calculate risk level based on probability and impact
useEffect(() => {
const calculateRiskLevel = () => {
const probValue = getRiskValue(formData.probability);
const impactValue = getRiskValue(formData.impact);
const total = probValue + impactValue;
if (total <= 4) {
setFormData((prev) => ({ ...prev, riskLevel: RiskLevelEnum.Low }));
} else if (total <= 6) {
setFormData((prev) => ({ ...prev, riskLevel: RiskLevelEnum.Medium }));
} else if (total <= 8) {
setFormData((prev) => ({ ...prev, riskLevel: RiskLevelEnum.High }));
} else {
setFormData((prev) => ({ ...prev, riskLevel: RiskLevelEnum.Critical }));
}
};
calculateRiskLevel();
}, [formData.probability, formData.impact]);
const getRiskValue = (risk: RiskProbabilityEnum | RiskImpactEnum): number => {
const values = {
VERY_LOW: 1,
LOW: 2,
MEDIUM: 3,
HIGH: 4,
VERY_HIGH: 5,
};
return values[risk as keyof typeof values] || 3;
};
const handleInputChange = (
e: React.ChangeEvent<
HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
>
) => {
const { name, value } = e.target;
setFormData((prev) => ({ ...prev, [name]: value }));
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const submitData: Partial<PsProjectRisk> = {
...formData,
riskCode: mode === "create" ? `RISK-${Date.now()}` : risk?.riskCode,
identifiedBy: mode === "create" ? "Current User" : risk?.identifiedBy,
identifiedDate: mode === "create" ? new Date() : risk?.identifiedDate,
reviewDate: formData.reviewDate
? new Date(formData.reviewDate)
: undefined,
isActive: true,
};
onSubmit(submitData);
handleClose();
};
const handleClose = () => {
setFormData({
title: "",
description: "",
category: RiskCategoryEnum.Technical,
probability: RiskProbabilityEnum.Medium,
impact: RiskImpactEnum.Medium,
riskLevel: RiskLevelEnum.Medium,
status: RiskStatusEnum.Identified,
mitigationPlan: "",
contingencyPlan: "",
ownerId: "",
reviewDate: "",
});
onClose();
};
if (!isOpen) return null;
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-4 w-full max-w-xl max-h-[90vh] overflow-y-auto">
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-semibold text-gray-900">
{mode === "edit" ? "Risk Düzenle" : "Yeni Risk Ekle"}
</h2>
<button
onClick={handleClose}
className="text-gray-400 hover:text-gray-600"
>
<FaTimes className="w-5 h-5" />
</button>
</div>
<form onSubmit={handleSubmit} className="space-y-3">
{/* Title */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Risk Başlığı *
</label>
<input
type="text"
name="title"
value={formData.title}
onChange={handleInputChange}
className="w-full px-3 py-1.5 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Risk başlığını girin"
required
/>
</div>
{/* Description */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
ıklama *
</label>
<textarea
name="description"
value={formData.description}
onChange={handleInputChange}
rows={2}
className="w-full px-3 py-1.5 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Risk açıklamasını girin"
required
/>
</div>
{/* Category, Probability, Impact */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Kategori
</label>
<select
name="category"
value={formData.category}
onChange={handleInputChange}
className="w-full px-3 py-1.5 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
{Object.values(RiskCategoryEnum).map((category) => (
<option key={category} value={category}>
{getRiskCategoryText(category)}
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Olasılık
</label>
<select
name="probability"
value={formData.probability}
onChange={handleInputChange}
className="w-full px-3 py-1.5 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
{Object.values(RiskProbabilityEnum).map((probability) => (
<option key={probability} value={probability}>
{getRiskProbabilityText(probability)}
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Etki
</label>
<select
name="impact"
value={formData.impact}
onChange={handleInputChange}
className="w-full px-3 py-1.5 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
{Object.values(RiskImpactEnum).map((impact) => (
<option key={impact} value={impact}>
{getRiskImpactText(impact)}
</option>
))}
</select>
</div>
</div>
{/* Risk Level (Auto-calculated) */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Risk Seviyesi (Otomatik Hesaplanan)
</label>
<div
className={`inline-flex items-center px-2.5 py-1.5 rounded-full text-sm font-medium ${getRiskLevelColor(
formData.riskLevel
)}`}
>
{getRiskLevelText(formData.riskLevel)}
</div>
</div>
{/* Status */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Durum
</label>
<select
name="status"
value={formData.status}
onChange={handleInputChange}
className="w-full px-3 py-1.5 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
{Object.values(RiskStatusEnum).map((status) => (
<option key={status} value={status}>
{getRiskStatusText(status)}
</option>
))}
</select>
</div>
{/* Mitigation Plan */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Önlem Planı
</label>
<textarea
name="mitigationPlan"
value={formData.mitigationPlan}
onChange={handleInputChange}
rows={2}
className="w-full px-3 py-1.5 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Risk için alınacak önlemleri açıklayın"
/>
</div>
{/* Contingency Plan */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Acil Durum Planı
</label>
<textarea
name="contingencyPlan"
value={formData.contingencyPlan}
onChange={handleInputChange}
rows={2}
className="w-full px-3 py-1.5 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Risk gerçekleşirse uygulanacak acil durum planınııklayın"
/>
</div>
{/* Review Date */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Değerlendirme Tarihi
</label>
<input
type="date"
name="reviewDate"
value={formData.reviewDate}
onChange={handleInputChange}
className="w-full px-3 py-1.5 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
{/* Actions */}
<div className="flex items-center justify-end space-x-2 pt-4">
<button
type="button"
onClick={handleClose}
className="px-3 py-1.5 text-sm font-medium text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-md"
>
İptal
</button>
<button
type="submit"
className="flex items-center space-x-2 px-3 py-1.5 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-md"
>
<FaSave className="w-4 h-4" />
<span>{mode === "edit" ? "Güncelle" : "Ekle"}</span>
</button>
</div>
</form>
</div>
</div>
);
};
export default RiskModal;