475 lines
15 KiB
JavaScript
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; |