319 lines
15 KiB
JavaScript
319 lines
15 KiB
JavaScript
import { Inertia } from "@inertiajs/inertia";
|
|
import React, { useState, useEffect } from "react";
|
|
|
|
const getImageUrl = (foto) => {
|
|
if (!foto) return null;
|
|
if (typeof foto === 'string') return `${foto}`;
|
|
return URL.createObjectURL(foto);
|
|
};
|
|
|
|
const ModalInput = ({ fields, tableName, options, initialData, onClose, showPayments = false }) => {
|
|
const [formData, setFormData] = useState({});
|
|
const [errors, setErrors] = useState({});
|
|
const [selectedPayments, setSelectedPayments] = useState([]);
|
|
const [paymentDetails, setPaymentDetails] = useState({});
|
|
|
|
useEffect(() => {
|
|
// console.log(initialData)
|
|
setFormData(initialData || {});
|
|
setSelectedPayments([]);
|
|
setPaymentDetails({});
|
|
}, [initialData]);
|
|
|
|
const handleChange = (e) => {
|
|
if (e.target.type === "file") {
|
|
setFormData({ ...formData, [e.target.name]: e.target.files[0] });
|
|
} else {
|
|
setFormData({ ...formData, [e.target.name]: e.target.value });
|
|
}
|
|
};
|
|
|
|
// console.log(initialData)
|
|
const handlePaymentChange = (e) => {
|
|
const typeId = e.target.value;
|
|
console.log(typeId)
|
|
|
|
if (!selectedPayments.includes(typeId)) {
|
|
setSelectedPayments([...selectedPayments, typeId]);
|
|
|
|
const nominal = parseInt(options.payment_nominal[typeId]) || 0;
|
|
const penalty = parseInt(options.payment_penalty[typeId]) || 0;
|
|
|
|
setPaymentDetails({
|
|
...paymentDetails,
|
|
[typeId]: {
|
|
range: 1,
|
|
nominal,
|
|
penalty,
|
|
amount: nominal + penalty,
|
|
},
|
|
});
|
|
}
|
|
};
|
|
|
|
const handleRangeChange = (paymentType, newRange) => {
|
|
setPaymentDetails((prevDetails) => {
|
|
const validRange = newRange && !isNaN(newRange) ? Math.max(1, Number(newRange)) : 1;
|
|
const currentDetails = prevDetails[paymentType] || { nominal: 0, penalty: 0 };
|
|
|
|
const newAmount = (currentDetails.nominal + currentDetails.penalty) * validRange;
|
|
|
|
return {
|
|
...prevDetails,
|
|
[paymentType]: {
|
|
...currentDetails,
|
|
range: validRange,
|
|
amount: newAmount,
|
|
},
|
|
};
|
|
});
|
|
};
|
|
|
|
const handleRemovePayment = (paymentType) => {
|
|
setSelectedPayments(selectedPayments.filter((p) => p !== paymentType));
|
|
const newDetails = { ...paymentDetails };
|
|
delete newDetails[paymentType];
|
|
setPaymentDetails(newDetails);
|
|
};
|
|
|
|
const handleSubmit = (e) => {
|
|
e.preventDefault();
|
|
|
|
const formDataObj = new FormData();
|
|
Object.keys(formData).forEach((key) => {
|
|
if (key === 'foto' && !(formData[key] instanceof File)) {
|
|
return;
|
|
}
|
|
formDataObj.append(key, formData[key]);
|
|
});
|
|
|
|
if (showPayments) {
|
|
const detailsArray = Object.entries(paymentDetails).map(([type_id, detail]) => ({
|
|
...detail,
|
|
type_id: parseInt(type_id)
|
|
}));
|
|
|
|
// Append items satu per satu
|
|
detailsArray.forEach((item, index) => {
|
|
formDataObj.append(`items[${index}][type_id]`, item.type_id);
|
|
formDataObj.append(`items[${index}][range]`, item.range);
|
|
// Kalau mau append nominal & penalty juga bisa, tapi biasanya backend gak butuh karena bisa hitung ulang
|
|
});
|
|
}
|
|
|
|
|
|
|
|
const url = initialData
|
|
? `/update${tableName}/${initialData.id}`
|
|
: `/add${tableName}`;
|
|
|
|
Inertia.post(url, formDataObj, {
|
|
forceFormData: true,
|
|
onError: (errors) => setErrors(errors),
|
|
onSuccess: () => {
|
|
document.getElementById('modal_input').checked = false;
|
|
setFormData({});
|
|
setErrors({});
|
|
onClose({});
|
|
},
|
|
});
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (formData.foto instanceof File) {
|
|
const url = URL.createObjectURL(formData.foto);
|
|
return () => URL.revokeObjectURL(url);
|
|
}
|
|
}, [formData.foto]);
|
|
|
|
|
|
|
|
return (
|
|
<div>
|
|
<input type="checkbox" id="modal_input" className="modal-toggle" />
|
|
<div className="modal" role="dialog">
|
|
<div className="modal-box max-w-lg">
|
|
<h2 className="font-bold text-xl text-center mb-6 border-b pb-2">
|
|
Form Data
|
|
</h2>
|
|
<form onSubmit={handleSubmit} encType="multipart/form-data" className="space-y-4">
|
|
{Object.entries(fields).map(([field, config]) => {
|
|
const type = typeof config === "string" ? config : config.type;
|
|
const readOnly = typeof config === "object" && config.readonly;
|
|
return (
|
|
<div key={field} className="form-control">
|
|
<label className="label font-semibold capitalize">{field.replace("_", " ")}</label>
|
|
{type === "select" ? (
|
|
<select
|
|
name={field}
|
|
value={formData[field] || ""}
|
|
onChange={handleChange}
|
|
className="select select-info focus:outline-none focus:ring-1 ring-info w-full"
|
|
>
|
|
<option disabled value="">
|
|
Pilih {field.replace("_", " ")}
|
|
</option>
|
|
{options[field] &&
|
|
Object.entries(options[field]).map(([key, value]) => (
|
|
<option key={key} value={key}>
|
|
{value}
|
|
</option>
|
|
))}
|
|
</select>
|
|
) : type === "file" ? (
|
|
<div>
|
|
<input
|
|
type="file"
|
|
name={field}
|
|
onChange={handleChange}
|
|
className="file-input file-input-info focus:outline-none focus:ring-1 ring-info w-full"
|
|
/>
|
|
{field === 'foto' && (
|
|
<div className="mt-2">
|
|
{formData.foto && (
|
|
<img
|
|
src={getImageUrl(formData.foto)}
|
|
alt="Preview Foto"
|
|
className="w-32 h-32 object-cover rounded-lg border"
|
|
/>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
) : type === "password" ? (
|
|
<input
|
|
type="password"
|
|
name={field}
|
|
onChange={handleChange}
|
|
value={formData[field] || ""}
|
|
className="input input-info focus:outline-none focus:ring-1 ring-info w-full"
|
|
placeholder={
|
|
initialData
|
|
? 'Kosongkan jika tidak ingin mengubah password'
|
|
: 'Masukkan password broh'
|
|
}
|
|
/>
|
|
|
|
) : (
|
|
<input
|
|
type={type}
|
|
name={field}
|
|
value={formData[field] || ""}
|
|
onChange={handleChange}
|
|
readOnly={readOnly}
|
|
className="input input-info focus:outline-none focus:ring-1 ring-info w-full"
|
|
placeholder={`Masukkan ${field.replace("_", " ")}`}
|
|
/>
|
|
)}
|
|
{errors[field] && (
|
|
<p className="text-red-500 text-sm mt-1">{errors[field]}</p>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
|
|
|
|
{showPayments && (
|
|
<div className="space-y-4 mt-4">
|
|
<div className="form-control">
|
|
<label className="label font-semibold">Payment Type</label>
|
|
<select
|
|
onChange={handlePaymentChange}
|
|
className="select select-info focus:outline-none focus:ring-1 ring-info w-full"
|
|
>
|
|
<option disabled value="">
|
|
Pilih Payment Type
|
|
</option>
|
|
{options.payment_type &&
|
|
Object.entries(options.payment_type).map(([id, name]) => (
|
|
<option key={id} value={id}>
|
|
{name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
|
|
{selectedPayments.map((paymentType) => (
|
|
<div
|
|
key={paymentType}
|
|
className="border rounded-lg p-4 relative bg-gray-50 shadow-sm"
|
|
>
|
|
<button
|
|
type="button"
|
|
onClick={() => handleRemovePayment(paymentType)}
|
|
className="absolute top-2 right-2 bg-red-500 hover:bg-red-600 text-white px-2 py-1 rounded-full text-xs"
|
|
>
|
|
X
|
|
</button>
|
|
|
|
<div className="form-control mb-2">
|
|
<label className="label font-semibold">
|
|
{options.payment_type[paymentType]} - Range Bulan
|
|
</label>
|
|
<input
|
|
type="number"
|
|
min="1"
|
|
value={paymentDetails[paymentType].range}
|
|
onChange={(e) =>
|
|
handleRangeChange(paymentType, parseInt(e.target.value))
|
|
}
|
|
className="input input-info w-full"
|
|
name="range"
|
|
/>
|
|
</div>
|
|
|
|
<div className="form-control mb-2">
|
|
<label className="label font-semibold">Nominal</label>
|
|
<input
|
|
type="text"
|
|
value={paymentDetails[paymentType].nominal}
|
|
readOnly
|
|
className="input bg-gray-100 w-full"
|
|
/>
|
|
</div>
|
|
|
|
<div className="form-control mb-2">
|
|
<label className="label font-semibold">Penalty</label>
|
|
<input
|
|
type="text"
|
|
value={paymentDetails[paymentType].penalty}
|
|
readOnly
|
|
className="input bg-gray-100 w-full"
|
|
/>
|
|
</div>
|
|
</div>
|
|
))}
|
|
|
|
<div className="form-control mt-4">
|
|
<label className="label font-semibold">Total Amount</label>
|
|
<input
|
|
type="text"
|
|
value={Object.values(paymentDetails).reduce(
|
|
(sum, p) => sum + (p.amount || 0),
|
|
0
|
|
)}
|
|
readOnly
|
|
className="input bg-gray-100 w-full"
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<button type="submit" className="btn btn-info text-white font-bold w-full mt-4">
|
|
{initialData ? "submit" : "Tambah Data"}
|
|
</button>
|
|
</form>
|
|
</div>
|
|
<label className="modal-backdrop" htmlFor="modal_input">
|
|
Close
|
|
</label>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ModalInput
|
|
|
|
|