273 lines
8.8 KiB
TypeScript
273 lines
8.8 KiB
TypeScript
|
|
import React from "react";
|
|||
|
|
import { useFormik } from "formik";
|
|||
|
|
import * as Yup from "yup";
|
|||
|
|
import { FaSave, FaTimes } from "react-icons/fa";
|
|||
|
|
import {
|
|||
|
|
QualityStatusEnum,
|
|||
|
|
MmLotNumber,
|
|||
|
|
MmUnit,
|
|||
|
|
MmMaterial,
|
|||
|
|
} from "../../../types/mm";
|
|||
|
|
import { BusinessParty } from "../../../types/common";
|
|||
|
|
|
|||
|
|
const validationSchema = Yup.object({
|
|||
|
|
materialId: Yup.string().required("Malzeme seçimi zorunlu"),
|
|||
|
|
lotNumber: Yup.string().required("Lot numarası zorunlu"),
|
|||
|
|
quantity: Yup.number().min(0, "Miktar 0'dan büyük olmalıdır"),
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
interface LotFormProps {
|
|||
|
|
isOpen: boolean;
|
|||
|
|
onClose: () => void;
|
|||
|
|
onSave: (lot: MmLotNumber) => void;
|
|||
|
|
onUpdate?: (lot: MmLotNumber) => void;
|
|||
|
|
materials: MmMaterial[];
|
|||
|
|
suppliers?: BusinessParty[];
|
|||
|
|
units?: MmUnit[];
|
|||
|
|
initial?: MmLotNumber | null;
|
|||
|
|
mode?: "create" | "edit" | "view";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const LotForm: React.FC<LotFormProps> = ({
|
|||
|
|
isOpen,
|
|||
|
|
onClose,
|
|||
|
|
onSave,
|
|||
|
|
onUpdate,
|
|||
|
|
materials,
|
|||
|
|
suppliers = [],
|
|||
|
|
units = [],
|
|||
|
|
initial = null,
|
|||
|
|
mode = "create",
|
|||
|
|
}) => {
|
|||
|
|
const formik = useFormik({
|
|||
|
|
initialValues: {
|
|||
|
|
materialId: "",
|
|||
|
|
lotNumber: "",
|
|||
|
|
productionDate: "",
|
|||
|
|
expiryDate: "",
|
|||
|
|
quantity: 0,
|
|||
|
|
unitId: "KG",
|
|||
|
|
supplierId: suppliers.length ? suppliers[0].id : "",
|
|||
|
|
qualityStatus: QualityStatusEnum.Pending,
|
|||
|
|
isActive: true,
|
|||
|
|
},
|
|||
|
|
validationSchema,
|
|||
|
|
onSubmit: async (values) => {
|
|||
|
|
const newLot: MmLotNumber = {
|
|||
|
|
id: (initial && initial.id) || Date.now().toString(),
|
|||
|
|
materialId: values.materialId,
|
|||
|
|
lotNumber: values.lotNumber,
|
|||
|
|
productionDate: values.productionDate
|
|||
|
|
? new Date(values.productionDate)
|
|||
|
|
: new Date(),
|
|||
|
|
expiryDate: values.expiryDate ? new Date(values.expiryDate) : undefined,
|
|||
|
|
quantity: Number(values.quantity),
|
|||
|
|
unitId: values.unitId,
|
|||
|
|
supplierId: values.supplierId || undefined,
|
|||
|
|
qualityStatus: values.qualityStatus,
|
|||
|
|
isActive: !!values.isActive,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// simulate API
|
|||
|
|
await new Promise((r) => setTimeout(r, 300));
|
|||
|
|
if (mode === "edit" && onUpdate) {
|
|||
|
|
onUpdate(newLot);
|
|||
|
|
} else {
|
|||
|
|
onSave(newLot);
|
|||
|
|
}
|
|||
|
|
onClose();
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// sync initial values when editing/viewing
|
|||
|
|
React.useEffect(() => {
|
|||
|
|
if (initial) {
|
|||
|
|
const src = initial;
|
|||
|
|
formik.setValues({
|
|||
|
|
materialId: src.materialId || "",
|
|||
|
|
lotNumber: src.lotNumber || "",
|
|||
|
|
productionDate: src.productionDate
|
|||
|
|
? new Date(src.productionDate).toISOString().slice(0, 10)
|
|||
|
|
: "",
|
|||
|
|
expiryDate: src.expiryDate
|
|||
|
|
? new Date(src.expiryDate).toISOString().slice(0, 10)
|
|||
|
|
: "",
|
|||
|
|
quantity: src.quantity || 0,
|
|||
|
|
unitId: src.unitId || (units.length ? units[0].id : ""),
|
|||
|
|
supplierId: src.supplierId || (suppliers.length ? suppliers[0].id : ""),
|
|||
|
|
qualityStatus: src.qualityStatus || QualityStatusEnum.Pending,
|
|||
|
|
isActive: !!src.isActive,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|||
|
|
}, [initial]);
|
|||
|
|
|
|||
|
|
if (!isOpen) return null;
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
|||
|
|
<div className="absolute inset-0 bg-black opacity-40" onClick={onClose} />
|
|||
|
|
<div className="relative bg-white rounded-lg shadow-lg w-full max-w-2xl mx-4">
|
|||
|
|
<form onSubmit={formik.handleSubmit} className="space-y-2 p-3">
|
|||
|
|
<div className="flex items-center justify-between p-2 border-b">
|
|||
|
|
<div>
|
|||
|
|
<h2 className="text-xl font-bold text-gray-900">
|
|||
|
|
{mode === "create"
|
|||
|
|
? "Yeni Lot Kaydı"
|
|||
|
|
: mode === "edit"
|
|||
|
|
? "Lot Düzenle"
|
|||
|
|
: "Lot Detayı"}
|
|||
|
|
</h2>
|
|||
|
|
<p className="text-sm text-gray-600">
|
|||
|
|
{mode === "create"
|
|||
|
|
? "Lot bilgilerini girin"
|
|||
|
|
: mode === "edit"
|
|||
|
|
? "Mevcut lot bilgilerini güncelleyin"
|
|||
|
|
: "Lot bilgileri (sadece gösterim)"}
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
<div className="flex items-center space-x-2">
|
|||
|
|
<button
|
|||
|
|
type="button"
|
|||
|
|
onClick={onClose}
|
|||
|
|
className="px-2.5 py-1 text-sm border rounded-md bg-white hover:bg-gray-50"
|
|||
|
|
>
|
|||
|
|
<FaTimes className="inline mr-1 h-3 w-3" /> Kapat
|
|||
|
|
</button>
|
|||
|
|
{mode !== "view" && (
|
|||
|
|
<button
|
|||
|
|
type="submit"
|
|||
|
|
className="px-2.5 py-1 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 flex items-center"
|
|||
|
|
>
|
|||
|
|
<FaSave className="inline mr-1 h-3 w-3" /> Kaydet
|
|||
|
|
</button>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="grid grid-cols-1 md:grid-cols-2 p-2 gap-x-3 gap-y-2">
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700">
|
|||
|
|
Malzeme *
|
|||
|
|
</label>
|
|||
|
|
<select
|
|||
|
|
{...formik.getFieldProps("materialId")}
|
|||
|
|
disabled={mode === "view"}
|
|||
|
|
className="w-full border rounded-md px-2 py-1 text-sm"
|
|||
|
|
>
|
|||
|
|
<option value="">Seçiniz...</option>
|
|||
|
|
{materials.map((m) => (
|
|||
|
|
<option key={m.id} value={m.id}>
|
|||
|
|
{m.code} - {m.name}
|
|||
|
|
</option>
|
|||
|
|
))}
|
|||
|
|
</select>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700">
|
|||
|
|
Lot Numarası *
|
|||
|
|
</label>
|
|||
|
|
<input
|
|||
|
|
type="text"
|
|||
|
|
{...formik.getFieldProps("lotNumber")}
|
|||
|
|
disabled={mode === "view"}
|
|||
|
|
className="w-full border rounded-md px-2 py-1 text-sm"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700">
|
|||
|
|
Üretim Tarihi
|
|||
|
|
</label>
|
|||
|
|
<input
|
|||
|
|
type="date"
|
|||
|
|
{...formik.getFieldProps("productionDate")}
|
|||
|
|
disabled={mode === "view"}
|
|||
|
|
className="w-full border rounded-md px-2 py-1 text-sm"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700">
|
|||
|
|
Son Kullanma Tarihi
|
|||
|
|
</label>
|
|||
|
|
<input
|
|||
|
|
type="date"
|
|||
|
|
{...formik.getFieldProps("expiryDate")}
|
|||
|
|
disabled={mode === "view"}
|
|||
|
|
className="w-full border rounded-md px-2 py-1 text-sm"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700">
|
|||
|
|
Miktar
|
|||
|
|
</label>
|
|||
|
|
<input
|
|||
|
|
type="number"
|
|||
|
|
step="0.01"
|
|||
|
|
{...formik.getFieldProps("quantity")}
|
|||
|
|
disabled={mode === "view"}
|
|||
|
|
className="w-full border rounded-md px-2 py-1 text-sm"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700">
|
|||
|
|
Birim
|
|||
|
|
</label>
|
|||
|
|
<select
|
|||
|
|
{...formik.getFieldProps("unitId")}
|
|||
|
|
disabled={mode === "view"}
|
|||
|
|
className="w-full border rounded-md px-2 py-1 text-sm"
|
|||
|
|
>
|
|||
|
|
{units.map((u) => (
|
|||
|
|
<option key={u.id} value={u.id}>
|
|||
|
|
{u.code}
|
|||
|
|
</option>
|
|||
|
|
))}
|
|||
|
|
</select>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700">
|
|||
|
|
Tedarikçi
|
|||
|
|
</label>
|
|||
|
|
<select
|
|||
|
|
{...formik.getFieldProps("supplierId")}
|
|||
|
|
disabled={mode === "view"}
|
|||
|
|
className="w-full border rounded-md px-2 py-1 text-sm"
|
|||
|
|
>
|
|||
|
|
<option value="">Seçiniz...</option>
|
|||
|
|
{suppliers.map((s) => (
|
|||
|
|
<option key={s.id} value={s.id}>
|
|||
|
|
{s.code} - {s.name}
|
|||
|
|
</option>
|
|||
|
|
))}
|
|||
|
|
</select>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700">
|
|||
|
|
Kalite Durumu
|
|||
|
|
</label>
|
|||
|
|
<select
|
|||
|
|
{...formik.getFieldProps("qualityStatus")}
|
|||
|
|
className="w-full border rounded-md px-2 py-1 text-sm"
|
|||
|
|
>
|
|||
|
|
<option value={QualityStatusEnum.Pending}>Beklemede</option>
|
|||
|
|
<option value={QualityStatusEnum.Approved}>Onaylandı</option>
|
|||
|
|
<option value={QualityStatusEnum.Rejected}>Reddedildi</option>
|
|||
|
|
<option value={QualityStatusEnum.Quarantine}>Karantina</option>
|
|||
|
|
</select>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</form>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
export default LotForm;
|