diff --git a/src/components/Tenant/CreateTenant.jsx b/src/components/Tenant/CreateTenant.jsx index b759f5ce..0391642e 100644 --- a/src/components/Tenant/CreateTenant.jsx +++ b/src/components/Tenant/CreateTenant.jsx @@ -1,12 +1,12 @@ 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 Breadcrumb from "../common/Breadcrumb"; import { apiTenant } from "./apiTenant"; import { useCreateTenant } from "./useTenants"; +import TenantSubscription from "./TenantSubscription"; const defaultAvatar = "https://via.placeholder.com/100x100.png?text=Avatar"; - const initialData = { firstName: "", lastName: "", @@ -24,64 +24,84 @@ const initialData = { onBoardingDate: "", }; +const RequiredLabel = ({ label, name }) => ( + +); + +const validateForm = (form, step) => { + let errors = {}; + let fieldsToValidate = []; + + if (step === 1) { + fieldsToValidate = [ + "firstName", + "lastName", + "email", + "phone", + "billingAddress", + ]; + } else if (step === 2) { + fieldsToValidate = [ + "organizationName", + "organizationSize", + "industryId", + "reference", + "domainName", + "onBoardingDate", + ]; + } + + fieldsToValidate.forEach((field) => { + if (!form?.[field] || String(form?.[field]).trim() === "") { + const fieldName = field.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase()); + errors[field] = `${fieldName} is required.`; + } + }); + + if (step === 1 && form.phone && !/^[0-9]{10}$/.test(form.phone)) { + errors.phone = "Phone number must be a 10-digit number."; + } + + return errors; +}; + const CreateTenant = () => { const navigate = useNavigate(); - const location = useLocation(); - const formData = location.state?.formData || null; - - const { createTenant, updateTenant, loading, error, success } = useCreateTenant(); + const { state } = useLocation(); + const formData = state?.formData || null; + const { createTenant, updateTenant, loading } = useCreateTenant(); const [form, setForm] = useState(initialData); + const [formErrors, setFormErrors] = useState({}); const [imagePreview, setImagePreview] = useState(defaultAvatar); const [imageFile, setImageFile] = useState(null); const [showImageModal, setShowImageModal] = useState(false); const [showImageSizeModal, setShowImageSizeModal] = useState(false); const [industryOptions, setIndustryOptions] = useState([]); + const [step, setStep] = useState(1); - // 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); - } + const [firstName, ...lastNameParts] = (contactName || "").trim().split(" "); + const lastName = lastNameParts.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); + const { data } = await apiTenant.getIndustries(); + if (Array.isArray(data)) { + setIndustryOptions(data); if (formData?.industry) { - const matchedIndustry = res.data.find( - (industry) => industry.name === formData.industry.name - ); - if (matchedIndustry) { - setForm((prev) => ({ ...prev, industryId: matchedIndustry.id })); - } + const matchedIndustry = data.find((i) => i.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); @@ -92,23 +112,28 @@ const CreateTenant = () => { const handleChange = (e) => { const { name, value } = e.target; - setForm((prev) => ({ ...prev, [name]: value })); + const sanitizedValue = name === "phone" ? value.replace(/\D/g, "") : value; + setForm((prev) => ({ ...prev, [name]: sanitizedValue })); + if (formErrors?.[name]) { + setFormErrors((prev) => { + const newErrors = { ...prev }; + delete newErrors?.[name]; + return newErrors; + }); + } }; 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 file = e.target.files?.[0]; + if (!file) return; + if (file.size > 200 * 1024) { + setShowImageSizeModal(true); + return; } + setImageFile(file); + const reader = new FileReader(); + reader.onloadend = () => setImagePreview(reader.result); + reader.readAsDataURL(file); }; const handleImageReset = () => { @@ -118,355 +143,234 @@ const CreateTenant = () => { const handleClearForm = () => { setForm(initialData); - setImagePreview(defaultAvatar); - setImageFile(null); + setFormErrors({}); + handleImageReset(); + setStep(1); + }; + + const handleNext = () => { + const errors = validateForm(form, step); + if (Object.keys(errors).length > 0) { + setFormErrors(errors); + return; + } + setFormErrors({}); + setStep((prev) => prev + 1); }; 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 errors = validateForm(form, 2); + if (Object.keys(errors).length > 0) { + setFormErrors(errors); + setStep(2); + return; + } + setFormErrors({}); + const finalLogoImage = 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."); - } + if (result) navigate("/tenant/profile", { state: { newTenant: result } }); } 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."); - } + if (result) navigate("/tenant/profile/viewtenant", { state: { formData: result } }); } }, [form, imagePreview, imageFile, formData, navigate, createTenant, updateTenant] ); - const RequiredLabel = ({ label }) => ( - - ); + const renderFormStep = () => { + switch (step) { + case 1: + return ( + <> +
+ + + {formErrors?.firstName && (

{formErrors.firstName}

)} +
+
+ + + {formErrors?.lastName && (

{formErrors.lastName}

)} +
+
+ + + {formErrors?.email && (

{formErrors.email}

)} +
+
+ + + {formErrors?.phone && (

{formErrors.phone}

)} +
+
+ + + {formErrors?.billingAddress && (

{formErrors.billingAddress}

)} +
+ + ); + case 2: + return ( + <> +
+ + + {formErrors?.onBoardingDate && (

{formErrors.onBoardingDate}

)} +
+
+ + + {formErrors?.organizationName && (

{form.organizationName}

)} +
+
+ + + {formErrors?.organizationSize && (

{formErrors.organizationSize}

)} +
+
+ + + {formErrors?.industryId && (

{formErrors.industryId}

)} +
+
+ + + {formErrors?.reference && (

{formErrors.reference}

)} +
+
+ + +
+
+ + + {formErrors?.domainName && (

{formErrors.domainName}

)} +
+
+ + +
+
+ + +
+
+
+ Profile Preview setShowImageModal(true)} style={{ width: "100px", height: "100px", objectFit: "cover", borderRadius: "8px", border: "1px solid #ccc", cursor: "pointer", }} /> + +
+
+
+ + +
+ Allowed JPG, GIF or PNG. Max size of 200KB +
+
+ + ); + case 3: + return ( +
+ +
+ ); + default: + return null; + } + }; + + const steps = [ + { label: "Contact Info", subtitle: "Provide contact details", icon: "bx-user" }, + { label: "Organization Details", subtitle: "Enter organization info", icon: "bx-buildings" }, + { label: "Subscription", subtitle: "Select a plan", icon: "bx-credit-card" }, + ]; return (
- - +
-
-
- {formData?.id ? "Update Tenant" : "Create Tenant"} -
- -
-
- {/* Form fields */} -
- - -
-
- - -
-
- - -
-
- - -
-
- - -
- -
- - -
- -
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
- -
- - -
- -
- - -
-
-
- Profile Preview setShowImageModal(true)} - style={{ - width: "100px", - height: "100px", - objectFit: "cover", - borderRadius: "8px", - border: "1px solid #ccc", - cursor: "pointer", - }} - /> +
+
+
{formData?.id ? "Update Tenant" : "Create Tenant"}
+ {step !== 3 && ()} +
+
+ {/* Steps Container with vertical divider */} +
+ {steps.map((s, index) => ( +
-
-
- - -
- Allowed JPG, GIF or PNG. Max size of 200KB + ))} +
+ {/* Form Content */} +
+ +
{renderFormStep()}
+
+ {step > 1 && ()} + {step < 3 && ()} + {step === 3 && ()}
-
+
- -
- - - {!formData?.id && ( - - )} - - {formData?.id && ( - - )} -
- +
- setShowImageModal(false)} centered size="lg"> - Preview + Preview - setShowImageSizeModal(false)} centered> - - Image Size Warning - - - The selected image file must be less than 200KB. Please choose a smaller file. - - - - + Image Size Warning + The selected image file must be less than 200KB. Please choose a smaller file. +
); diff --git a/src/components/Tenant/Tenant.jsx b/src/components/Tenant/Tenant.jsx index 5116096f..944f2166 100644 --- a/src/components/Tenant/Tenant.jsx +++ b/src/components/Tenant/Tenant.jsx @@ -11,25 +11,20 @@ const Tenant = () => { const navigate = useNavigate(); const location = useLocation(); - // This useEffect syncs the fetched tenants with the local state. useEffect(() => { if (tenants) { setLocalTenants(tenants); } }, [tenants]); - // This useEffect handles adding or updating a tenant from the location state. useEffect(() => { const newTenant = location.state?.newTenant; if (newTenant) { setLocalTenants((prev) => { - // Find if the tenant already exists by ID const tenantIndex = prev.findIndex((t) => t.id === newTenant.id); if (tenantIndex > -1) { - // If exists, update the tenant return prev.map((t) => (t.id === newTenant.id ? newTenant : t)); } else { - // If not, add a new tenant with a generated ID return [...prev, { ...newTenant, id: newTenant.id || Date.now() }]; } }); @@ -48,17 +43,14 @@ const Tenant = () => { navigate("/tenant/profile/viewtenant", { state: { formData: tenant } }); }; - // This function handles tenant deletion from the local state. const handleDelete = (id) => { if (window.confirm("Are you sure you want to delete this tenant?")) { setLocalTenants((prev) => prev.filter((t) => t.id !== id)); } }; - // Utility function for case-insensitive and whitespace-insensitive search const normalize = (str) => (str?.toLowerCase().replace(/\s+/g, "") || ""); - // Filters the tenants based on the search term const filteredTenants = localTenants.filter((tenant) => { const term = normalize(searchTerm); return ( @@ -67,7 +59,7 @@ const Tenant = () => { normalize(tenant.contactNumber).includes(term) || normalize(tenant.domainName).includes(term) || normalize(tenant.name).includes(term) || - normalize(tenant.oragnizationSize).includes(term) || + normalize(tenant.organizationSize).includes(term) || normalize(tenant.industry?.name).includes(term) ); }); @@ -98,18 +90,21 @@ const Tenant = () => {
-
- +
+
- - - - - - - - + + + {/* */} + + {/* */} + + + @@ -120,7 +115,8 @@ const Tenant = () => { return ( - + - - - - - - - */} + + {/* */} + {/* */} + + + + + diff --git a/src/components/Tenant/TenantSubscription.jsx b/src/components/Tenant/TenantSubscription.jsx index 81e1713f..048c8463 100644 --- a/src/components/Tenant/TenantSubscription.jsx +++ b/src/components/Tenant/TenantSubscription.jsx @@ -1,84 +1,310 @@ -import React from "react"; -import Breadcrumb from "../common/Breadcrumb"; // ✅ Adjust path if needed +import React, { useState } from "react"; +// Assuming Breadcrumb is a custom component and pricingData is defined globally or imported +// For this example, we'll define pricingData and a mock Breadcrumb component +const Breadcrumb = () => ( + +); -const plans = [ - { - name: "Basic", - price: "$0", - per: "/month", - description: "A simple start for everyone", - features: [ - "100 responses a month", - "Unlimited forms and surveys", - "Unlimited fields", - "Basic form creation tools", - "Up to 2 subdomains", - ], - button: "Your Current Plan", - buttonClass: "btn btn-success disabled", - highlight: false, - }, - { - name: "Standard", - price: "$40", - per: "/month", - description: "For small to medium businesses", - subText: "USD 480/year", - features: [ - "Unlimited responses", - "Unlimited forms and surveys", - "Instagram profile page", - "Google Docs integration", - 'Custom "Thank you" page', - ], - button: "Upgrade", - buttonClass: "btn btn-primary", - highlight: true, - badge: "Popular", - }, - { - name: "Enterprise", - price: "$80", - per: "/month", - description: "Solution for big organizations", - subText: "USD 960/year", - features: [ - "PayPal payments", - "Logic Jumps", - "File upload with 5GB storage", - "Custom domain support", - "Stripe integration", - ], - button: "Buy Now", - buttonClass: "btn btn-warning text-white fw-semibold shadow", - highlight: false, - }, -]; +const pricingData = { + "Monthly": [ + { + name: "Basic", + price: "$0", + per: "/month", + description: "A simple start for everyone", + features: [ + "100 responses a month", + "Unlimited forms and surveys", + "Unlimited fields", + "Basic form creation tools", + "Up to 2 subdomains", + ], + button: "Your Current Plan", + buttonClass: "btn btn-success disabled", + highlight: false, + }, + { + name: "Standard", + price: "$45", + per: "/month", + description: "For small to medium businesses", + subText: "Billed monthly", + features: [ + "Unlimited responses", + "Unlimited forms and surveys", + "Instagram profile page", + "Google Docs integration", + 'Custom "Thank you" page', + ], + button: "Upgrade", + buttonClass: "btn btn-primary", + highlight: true, + badge: "Popular", + }, + { + name: "Enterprise", + price: "$90", + per: "/month", + description: "Solution for big organizations", + subText: "Billed monthly", + features: [ + "PayPal payments", + "Logic Jumps", + "File upload with 5GB storage", + "Custom domain support", + "Stripe integration", + ], + button: "Buy Now", + buttonClass: "btn btn-warning text-white fw-semibold shadow", + highlight: false, + }, + ], + "Quarterly": [ + { + name: "Basic", + price: "$0", + per: "/quarter", + description: "A simple start for everyone", + features: [ + "100 responses a month", + "Unlimited forms and surveys", + "Unlimited fields", + "Basic form creation tools", + "Up to 2 subdomains", + ], + button: "Your Current Plan", + buttonClass: "btn btn-success disabled", + highlight: false, + }, + { + name: "Standard", + price: "$120", + per: "/quarter", + description: "For small to medium businesses", + subText: "Billed quarterly", + features: [ + "Unlimited responses", + "Unlimited forms and surveys", + "Instagram profile page", + "Google Docs integration", + 'Custom "Thank you" page', + ], + button: "Upgrade", + buttonClass: "btn btn-primary", + highlight: true, + badge: "Popular", + }, + { + name: "Enterprise", + price: "$240", + per: "/quarter", + description: "Solution for big organizations", + subText: "Billed quarterly", + features: [ + "PayPal payments", + "Logic Jumps", + "File upload with 5GB storage", + "Custom domain support", + "Stripe integration", + ], + button: "Buy Now", + buttonClass: "btn btn-warning text-white fw-semibold shadow", + highlight: false, + }, + ], + "Half-Yearly": [ + { + name: "Basic", + price: "$0", + per: "/6 months", + description: "A simple start for everyone", + features: [ + "100 responses a month", + "Unlimited forms and surveys", + "Unlimited fields", + "Basic form creation tools", + "Up to 2 subdomains", + ], + button: "Your Current Plan", + buttonClass: "btn btn-success disabled", + highlight: false, + }, + { + name: "Standard", + price: "$220", + per: "/6 months", + description: "For small to medium businesses", + subText: "USD 220 every 6 months", + features: [ + "Unlimited responses", + "Unlimited forms and surveys", + "Instagram profile page", + "Google Docs integration", + 'Custom "Thank you" page', + ], + button: "Upgrade", + buttonClass: "btn btn-primary", + highlight: true, + badge: "Popular", + }, + { + name: "Enterprise", + price: "$440", + per: "/6 months", + description: "Solution for big organizations", + subText: "USD 440 every 6 months", + features: [ + "PayPal payments", + "Logic Jumps", + "File upload with 5GB storage", + "Custom domain support", + "Stripe integration", + ], + button: "Buy Now", + buttonClass: "btn btn-warning text-white fw-semibold shadow", + highlight: false, + }, + ], + "Yearly": [ + { + name: "Basic", + price: "$0", + per: "/year", + description: "A simple start for everyone", + features: [ + "100 responses a month", + "Unlimited forms and surveys", + "Unlimited fields", + "Basic form creation tools", + "Up to 2 subdomains", + ], + button: "Your Current Plan", + buttonClass: "btn btn-success disabled", + highlight: false, + }, + { + name: "Standard", + price: "$400", + per: "/year", + description: "For small to medium businesses", + subText: "USD 400/year", + features: [ + "Unlimited responses", + "Unlimited forms and surveys", + "Instagram profile page", + "Google Docs integration", + 'Custom "Thank you" page', + ], + button: "Upgrade", + buttonClass: "btn btn-primary", + highlight: true, + badge: "Popular", + }, + { + name: "Enterprise", + price: "$800", + per: "/year", + description: "Solution for big organizations", + subText: "USD 800/year", + features: [ + "PayPal payments", + "Logic Jumps", + "File upload with 5GB storage", + "Custom domain support", + "Stripe integration", + ], + button: "Buy Now", + buttonClass: "btn btn-warning text-white fw-semibold shadow", + highlight: false, + }, + ], +}; +const billingOptions = ["Monthly", "Quarterly", "Half-Yearly", "Yearly"]; +const organizationSizes = ["1-10", "11-50", "51-200", "201-500", "501+"]; const TenantSubscription = () => { + const [selectedBillingIndex, setSelectedBillingIndex] = useState(null); + const [selectedPlanIndex, setSelectedPlanIndex] = useState(null); + const [selectedOrgSize, setSelectedOrgSize] = useState(""); + + const selectedBillingKey = billingOptions[selectedBillingIndex]; + const plansForBilling = pricingData[selectedBillingKey]; + + const currentPlan = plansForBilling ? plansForBilling[selectedPlanIndex] : null; + return (
- {/* ✅ Breadcrumb */} - + + +
+

Choose your billing cycle:

+
+ {billingOptions.map((option, index) => ( + + ))} +
+
+ + {plansForBilling && ( +
+

Select a plan:

+
+ {plansForBilling.map((plan, index) => ( + + ))} +
+
+ )} -
- {plans.map((plan, idx) => ( -
+
+ {currentPlan && ( +
-
- {plan.badge && ( +
+ {currentPlan.badge && ( - {plan.badge} + {currentPlan.badge} )}
@@ -87,28 +313,47 @@ const TenantSubscription = () => { style={{ fontSize: "48px", color: "#6366f1" }} >
-
{plan.name}
-

{plan.description}

+
{currentPlan.name}
+

{currentPlan.description}

- {plan.price} {plan.per} + {currentPlan.price}{" "} + {currentPlan.per}

- {plan.subText &&

{plan.subText}

} -
    - {plan.features.map((feat, i) => ( + {currentPlan.subText &&

    {currentPlan.subText}

    } +
      + {currentPlan.features.map((feat, i) => (
    • {feat}
    • ))}
    - +
- ))} + )} +
+ + {/* Moved the Organization Size dropdown to appear after the details section */} +
+ +
); }; -export default TenantSubscription; +export default TenantSubscription; \ No newline at end of file diff --git a/src/components/Tenant/ViewTenant.jsx b/src/components/Tenant/ViewTenant.jsx index 478ae5dd..f8e6ea39 100644 --- a/src/components/Tenant/ViewTenant.jsx +++ b/src/components/Tenant/ViewTenant.jsx @@ -46,7 +46,7 @@ const ViewTenant = () => { aria-controls="profile-details" aria-selected={activeTab === "profile"} > - Profile Details + Tenant Profile
  • @@ -79,7 +79,7 @@ const ViewTenant = () => {
    - + Contact Name : @@ -90,7 +90,7 @@ const ViewTenant = () => {
    - + Email : @@ -101,7 +101,7 @@ const ViewTenant = () => {
    - + Phone : @@ -112,7 +112,7 @@ const ViewTenant = () => {
    - + Domain : @@ -123,7 +123,7 @@ const ViewTenant = () => {
    - + Organization : @@ -134,18 +134,18 @@ const ViewTenant = () => {
    - + Size :
    - {tenant.oragnizationSize || "N/A"} + {tenant.organizationSize || "N/A"}
    {/* Industry */}
    - + Industry : @@ -156,7 +156,7 @@ const ViewTenant = () => {
    - + Status : @@ -182,7 +182,7 @@ const ViewTenant = () => {
    - + Plan : @@ -193,7 +193,7 @@ const ViewTenant = () => {
    - + Start Date : @@ -204,7 +204,7 @@ const ViewTenant = () => {
    - + End Date : @@ -215,7 +215,7 @@ const ViewTenant = () => {
    - + Status : @@ -226,7 +226,7 @@ const ViewTenant = () => {
    - + Seats : @@ -237,7 +237,7 @@ const ViewTenant = () => {
    - + Billing : diff --git a/src/pages/employee/EmployeeList.jsx b/src/pages/employee/EmployeeList.jsx index 73ef5901..07a8dd42 100644 --- a/src/pages/employee/EmployeeList.jsx +++ b/src/pages/employee/EmployeeList.jsx @@ -234,58 +234,7 @@ const EmployeeList = () => { return ( <> - {EmpForManageRole && ( - setEmpForManageRole(null)} - > - setEmpForManageRole(null)} - /> - - )} - - {showModal && ( - setShowModal(false)} - > - setShowModal(false)} - IsAllEmployee={showAllEmployees} - /> - - )} - - {IsDeleteModalOpen && ( -
    - setIsDeleteModalOpen(false)} - loading={employeeLodaing} - paramData={selectedEmpFordelete} - /> -
    - )} - -
    +
    { {/* Switches: All Employees + Inactive */}
    {/* All Employees Switch */} - {ViewAllEmployee && ( -
    - - -
    - )} + {/* Show Inactive Employees Switch */} - {showAllEmployees && ( -
    - setShowInactive(e.target.checked)} - /> - -
    - )} +
    {/* Right side: Search + Export + Add Employee */} @@ -363,69 +278,9 @@ const EmployeeList = () => {
    {/* Export Dropdown */} - - + {/* Add Employee Button */} - {Manage_Employee && ( - - )} +
  • NameEmailPhoneDomainOrganizationSizeIndustryActionsOrganizationNameEmailPhoneDomainSizeIndustryActions
    + {tenant.name}
    {tenant.logoImage ? (
    {
    {tenant.email}{tenant.contactNumber}{tenant.domainName}{tenant.name}{tenant.oragnizationSize}{tenant.industry?.name} -
    - -
      -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    + {/*
    {tenant.email}{tenant.contactNumber}{tenant.domainName} + {tenant.domainName ? ( + <> + + {tenant.domainName} + + ) : ( + -- + )} + {tenant.organizationSize}{tenant.industry?.name} +
    + handleView(tenant)} + > + handleEdit(tenant)} + > + handleDelete(tenant.id)} + >