marco.pms.web/src/components/Tenant/CreateTenant.jsx

475 lines
15 KiB
JavaScript

import React, { useEffect, useState, useCallback } from "react";
import { useNavigate, useLocation } from "react-router-dom";
import Breadcrumb from "../common/Breadcrumb";
import { Modal } from "react-bootstrap";
import { apiTenant } from "./apiTenant";
import { useCreateTenant } from "./useTenants";
const defaultAvatar = "https://via.placeholder.com/100x100.png?text=Avatar";
const initialData = {
firstName: "",
lastName: "",
email: "",
phone: "",
mobile: "",
domainName: "",
organizationName: "",
description: "",
organizationSize: "",
industryId: "",
reference: "",
taxId: "",
billingAddress: "",
onBoardingDate: "",
};
const CreateTenant = () => {
const navigate = useNavigate();
const location = useLocation();
const formData = location.state?.formData || null;
const { createTenant, updateTenant, loading, error, success } = useCreateTenant();
const [form, setForm] = useState(initialData);
const [imagePreview, setImagePreview] = useState(defaultAvatar);
const [imageFile, setImageFile] = useState(null);
const [showImageModal, setShowImageModal] = useState(false);
const [showImageSizeModal, setShowImageSizeModal] = useState(false);
const [industryOptions, setIndustryOptions] = useState([]);
// Load form data if it's passed via location state
useEffect(() => {
if (formData) {
const { contactName, contactNumber, logoImage, ...rest } = formData;
let firstName = "";
let lastName = "";
if (contactName) {
const nameParts = contactName.trim().split(" ");
firstName = nameParts.shift() || "";
lastName = nameParts.join(" ") || "";
}
setForm({
...initialData,
...rest,
firstName,
lastName,
phone: contactNumber || "",
});
if (logoImage) {
setImagePreview(logoImage);
}
}
}, [formData]);
// Load industry options from the API when the component mounts
useEffect(() => {
const fetchIndustries = async () => {
try {
const res = await apiTenant.getIndustries();
if (Array.isArray(res.data)) {
setIndustryOptions(res.data);
if (formData?.industry) {
const matchedIndustry = res.data.find(
(industry) => industry.name === formData.industry.name
);
if (matchedIndustry) {
setForm((prev) => ({ ...prev, industryId: matchedIndustry.id }));
}
}
} else {
console.error("Unexpected response format for industries", res);
}
} catch (err) {
console.error("Failed to load industries:", err);
}
};
fetchIndustries();
}, [formData]);
const handleChange = (e) => {
const { name, value } = e.target;
setForm((prev) => ({ ...prev, [name]: value }));
};
const handleImageChange = (e) => {
const file = e.target.files[0];
if (file) {
if (file.size <= 200 * 1024) {
setImageFile(file);
const reader = new FileReader();
reader.onloadend = () => {
setImagePreview(reader.result);
};
reader.readAsDataURL(file);
} else {
setShowImageSizeModal(true);
}
}
};
const handleImageReset = () => {
setImageFile(null);
setImagePreview(defaultAvatar);
};
const handleClearForm = () => {
setForm(initialData);
setImagePreview(defaultAvatar);
setImageFile(null);
};
const handleSubmit = useCallback(
async (e) => {
e.preventDefault();
// Determine the image to send to the API
// If there's a new file selected (imageFile), use it.
// If the image was reset (imagePreview is defaultAvatar), send null.
// Otherwise, use the existing logo image (imagePreview).
const finalLogoImage = imageFile
? imageFile
: imagePreview === defaultAvatar
? null
: imagePreview;
const submissionData = {
...form,
logoImage: finalLogoImage,
contactNumber: form.phone,
contactName: `${form.firstName} ${form.lastName}`.trim(),
};
let result;
if (formData?.id) {
result = await updateTenant(formData.id, submissionData);
if (result) {
alert("Tenant updated successfully!");
navigate("/tenant/profile", { state: { newTenant: result } });
} else {
alert("Failed to update tenant. Please check the form and try again.");
}
} else {
result = await createTenant(submissionData);
if (result) {
alert("Tenant created successfully!");
navigate("/tenant/profile/subscription", { state: { formData: result } });
} else {
alert("Failed to create tenant. Please check the form and try again.");
}
}
},
[form, imagePreview, imageFile, formData, navigate, createTenant, updateTenant]
);
const RequiredLabel = ({ label }) => (
<label className="form-label small mb-1">
{label} <span className="text-danger">*</span>
</label>
);
return (
<div className="container py-3">
<Breadcrumb
data={[
{ label: "Home", link: "/" },
{ label: "Tenant", link: "/tenant/profile" },
{ label: formData?.id ? "Update Tenant" : "Create Tenant" },
]}
/>
<div className="card rounded-3 shadow-sm mt-3">
<div className="card-body p-3 mx-6">
<h5 className="text-start mb-3">
{formData?.id ? "Update Tenant" : "Create Tenant"}
</h5>
<form onSubmit={handleSubmit}>
<div className="row g-4 text-start">
{/* Form fields */}
<div className="col-md-6">
<RequiredLabel label="First Name" />
<input
type="text"
name="firstName"
className="form-control form-control-sm"
value={form.firstName}
onChange={handleChange}
required
/>
</div>
<div className="col-md-6">
<RequiredLabel label="Last Name" />
<input
type="text"
name="lastName"
className="form-control form-control-sm"
value={form.lastName}
onChange={handleChange}
required
/>
</div>
<div className="col-md-6">
<RequiredLabel label="Email" />
<input
type="email"
name="email"
className="form-control form-control-sm"
value={form.email}
onChange={handleChange}
required
/>
</div>
<div className="col-md-3">
<RequiredLabel label="Phone" />
<input
type="text"
name="phone"
className="form-control form-control-sm"
value={form.phone}
onChange={handleChange}
required
/>
</div>
<div className="col-md-3">
<label className="form-label small mb-1">Landline Number</label>
<input
type="text"
name="mobile"
className="form-control form-control-sm"
value={form.mobile}
onChange={handleChange}
/>
</div>
<div className="col-md-6">
<RequiredLabel label="Onboarding Date" />
<input
type="date"
name="onBoardingDate"
className="form-control form-control-sm"
value={form.onBoardingDate}
onChange={handleChange}
required
/>
</div>
<div className="col-md-6">
<RequiredLabel label="Organization Name" />
<input
type="text"
name="organizationName"
className="form-control form-control-sm"
value={form.organizationName}
onChange={handleChange}
required
/>
</div>
<div className="col-md-4">
<RequiredLabel label="Organization Size" />
<select
name="organizationSize"
className="form-select form-select-sm"
value={form.organizationSize}
onChange={handleChange}
required
>
<option value="">Select</option>
<option>1-50</option>
<option>51-100</option>
<option>101-500</option>
<option>500+</option>
</select>
</div>
<div className="col-md-4">
<RequiredLabel label="Industry" />
<select
name="industryId"
className="form-select form-select-sm"
value={form.industryId}
onChange={handleChange}
required
>
<option value="">Select</option>
{industryOptions.map((industry) => (
<option key={industry.id} value={industry.id}>
{industry.name}
</option>
))}
</select>
</div>
<div className="col-md-4">
<RequiredLabel label="Reference" />
<select
name="reference"
className="form-select form-select-sm"
value={form.reference}
onChange={handleChange}
required
>
<option value="">Select</option>
<option>Google</option>
<option>Friend</option>
<option>Advertisement</option>
<option>Root Tenant</option>
</select>
</div>
<div className="col-md-6">
<label className="form-label small mb-1">Tax ID</label>
<input
type="text"
name="taxId"
className="form-control form-control-sm"
value={form.taxId}
onChange={handleChange}
/>
</div>
<div className="col-md-6">
<RequiredLabel label="Domain Name" />
<input
type="text"
name="domainName"
className="form-control form-control-sm"
value={form.domainName}
onChange={handleChange}
required
/>
</div>
<div className="col-12">
<RequiredLabel label="Billing Address" />
<textarea
name="billingAddress"
className="form-control form-control-sm"
rows="2"
value={form.billingAddress}
onChange={handleChange}
required
></textarea>
</div>
<div className="col-12">
<label className="form-label small mb-1">Description</label>
<textarea
name="description"
className="form-control form-control-sm"
rows="2"
value={form.description}
onChange={handleChange}
></textarea>
</div>
<div className="mb-0 text-start d-flex align-items-start gap-3 position-relative">
<div style={{ position: "relative", width: "100px", height: "100px" }}>
<img
src={imagePreview}
alt="Profile Preview"
onClick={() => setShowImageModal(true)}
style={{
width: "100px",
height: "100px",
objectFit: "cover",
borderRadius: "8px",
border: "1px solid #ccc",
cursor: "pointer",
}}
/>
<button
type="button"
className="btn btn-sm btn-light position-absolute"
onClick={handleImageReset}
style={{
top: "-10px",
right: "-10px",
padding: "0.25rem 0.5rem",
borderRadius: "50%",
boxShadow: "0 0 3px rgba(0,0,0,0.3)",
}}
title="Delete Photo"
>
<i className="bx bx-trash text-danger"></i>
</button>
</div>
<div>
<div className="mb-2">
<input
type="file"
accept="image/png, image/jpeg, image/gif"
onChange={handleImageChange}
style={{ display: "none" }}
id="upload-photo"
/>
<label htmlFor="upload-photo" className="btn btn-sm btn-primary me-2">
Upload New Photo
</label>
</div>
<small className="text-muted">Allowed JPG, GIF or PNG. Max size of 200KB</small>
</div>
</div>
</div>
<div className="mt-4 text-center">
<button
type="submit"
className="btn btn-sm btn-primary px-4"
disabled={loading}
>
{loading ? "Saving..." : formData?.id ? "Update" : "Save & Continue"}
</button>
{!formData?.id && (
<button
type="button"
className="btn btn-sm btn-warning ms-2 px-4"
onClick={handleClearForm}
>
Clear
</button>
)}
{formData?.id && (
<button
type="button"
className="btn btn-sm btn-secondary ms-2 px-4"
onClick={() => navigate("/tenant/profile")}
>
Cancel
</button>
)}
</div>
</form>
</div>
</div>
<Modal show={showImageModal} onHide={() => setShowImageModal(false)} centered size="lg">
<Modal.Body className="text-center">
<img
src={imagePreview}
alt="Preview"
style={{ width: "100%", height: "auto", borderRadius: "8px" }}
/>
</Modal.Body>
</Modal>
<Modal show={showImageSizeModal} onHide={() => setShowImageSizeModal(false)} centered>
<Modal.Header closeButton>
<Modal.Title>Image Size Warning</Modal.Title>
</Modal.Header>
<Modal.Body>
The selected image file must be less than 200KB. Please choose a smaller file.
</Modal.Body>
<Modal.Footer>
<button className="btn btn-primary" onClick={() => setShowImageSizeModal(false)}>
Close
</button>
</Modal.Footer>
</Modal>
</div>
);
};
export default CreateTenant;