added Tenant Profile

This commit is contained in:
pramod mahajan 2025-08-11 20:24:04 +05:30
parent 2d7adbac14
commit d25cc3531e
12 changed files with 344 additions and 162 deletions

View File

@ -0,0 +1,17 @@
import React from 'react'
import { formatUTCToLocalTime } from '../../utils/dateUtils'
const Organization = ({data}) => {
return (
<div className='container-fluid'>
{/* <div className='col-12'>
<h4>{data?.name}</h4>
</div> */}
</div>
)
}
export default Organization

View File

@ -4,6 +4,8 @@ import Label from "../common/Label";
import DatePicker from "../common/DatePicker";
import { useCreateTenant, useIndustries } from "../../hooks/useTenant";
import { LogoUpload } from "./LogoUpload";
import { orgSize, reference } from "../../utils/constants";
import moment from "moment";
const OrganizationInfo = ({ onNext, onPrev, onSubmitTenant }) => {
const { data, isError, isLoading: industryLoading } = useIndustries();
@ -40,26 +42,15 @@ const OrganizationInfo = ({ onNext, onPrev, onSubmitTenant }) => {
]);
if (valid) {
const tenantPayload = getValues();
const data = getValues();
// onSubmitTenant(data);
// onNext();
const tenantPayload = {...data,onBoardingDate: moment.utc(data.onBoardingDate, "DD-MM-YYYY").toISOString() }
CreateTenant(tenantPayload);
}
};
const Reference = [
{ val: "google", name: "Google" },
{ val: "frined", name: "Friend" },
{ val: "advertisement", name: "Advertisement" },
{ val: "root tenant", name: "Root Tenant" },
];
const orgSize = [
{ val: "50", name: "1-50" },
{ val: "100", name: "51-100" },
{ val: "500", name: "101-500" },
{ val: "600", name: "500+" },
];
return (
<div className="row g-2">
@ -74,9 +65,7 @@ const OrganizationInfo = ({ onNext, onPrev, onSubmitTenant }) => {
{...register("organizationName")}
/>
{errors.organizationName && (
<div className="danger-text">
{errors.organizationName.message}
</div>
<div className="danger-text">{errors.organizationName.message}</div>
)}
</div>
@ -126,43 +115,28 @@ const OrganizationInfo = ({ onNext, onPrev, onSubmitTenant }) => {
<Label htmlFor="onBoardingDate" required>
Onboarding Date
</Label>
{/* this will upcomming from main */}
{/* <DatePicker
name="onBoardingDate"
control={control}
placeholder="Select onboarding date"
maxDate={new Date()}
className={errors.onBoardingDate ? "is-invalid" : ""}
/>
{errors.onBoardingDate && (
<div className="invalid-feedback">{errors.onBoardingDate.message}</div>
)} */}
<input
type="date"
id="onBoardingDate"
{...register("onBoardingDate")}
className={`form-control form-control-sm `}
<DatePicker
name="onBoardingDate"
control={control}
placeholder="DD-MM-YYYY"
maxDate={new Date()}
className={errors.onBoardingDate ? "is-invalid" : ""}
/>
{errors.onBoardingDate && (
<div className="danger-text">
<div className="invalid-feedback">
{errors.onBoardingDate.message}
</div>
)}
{errors.onBoardingDate && (
<div className="danger-text">{errors.onBoardingDate.message}</div>
)}
</div>
<div className="col-sm-6">
<Label htmlFor="organizationSize" required>
Organization Size
</Label>
{/* <input
id="organizationSize"
type="number"
className={`form-control form-control-sm ${
errors.organizationSize ? "is-invalid" : ""
}`}
{...register("organizationSize")}
/> */}
<select
className="form-select form-select-sm"
@ -175,9 +149,7 @@ const OrganizationInfo = ({ onNext, onPrev, onSubmitTenant }) => {
))}
</select>
{errors.organizationSize && (
<div className="danger-text">
{errors.organizationSize.message}
</div>
<div className="danger-text">{errors.organizationSize.message}</div>
)}
</div>
@ -206,18 +178,12 @@ const OrganizationInfo = ({ onNext, onPrev, onSubmitTenant }) => {
<div className="col-sm-6">
<Label htmlFor="reference">Reference</Label>
{/* <input
id="reference"
className={`form-control form-control-sm ${
errors.reference ? "is-invalid" : ""
}`}
{...register("reference")}
/> */}
<select
className="form-select form-select-sm"
{...register("reference")}
>
{Reference.map((org) => (
{reference.map((org) => (
<option key={org.val} value={org.val}>
{org.name}
</option>
@ -267,7 +233,7 @@ const OrganizationInfo = ({ onNext, onPrev, onSubmitTenant }) => {
onClick={handleNext}
disabled={isPending}
>
{isPending ? "Please Wait...":"Submit and Next"}
{isPending ? "Please Wait..." : "Submit and Next"}
</button>
</div>
</div>

View File

@ -1,9 +1,156 @@
import React from 'react'
import React from "react";
import { formatUTCToLocalTime } from "../../utils/dateUtils";
const Profile = () => {
const Profile = ({ data }) => {
// const {logoImage,name,domainName,contactName,contactNumber,officeNumber} = data;
return (
<div className='container-fuid'>profile</div>
)
}
<div className="container-fuid">
<div className="row">
<div className="col-12 ">
<div className="d-flex flex-wrap align-items-start position-relative ">
<div className=" d-flex align-items-start gap-2">
<img
src={data.logoImage}
alt="Preview"
className="img-thumbnail rounded"
style={{ maxHeight: "35px" }}
/>
</div>
<div className="ms-2 ">
<h4 className="m-0">{data.name}</h4>
<div className="block">
<i className="bx bx-globe text-primary bx-xs me-1"></i>
<span>{data?.domainName}</span>
</div>
<span className="badge text-bg-primary position-absolute top-0 end-0">
{data?.tenantStatus?.name}
</span>
</div>
</div>
</div>
<div className="divider text-start">
<div className="divider-text">Profile</div>
</div>
export default Profile
<div className="col rounded-2 bg-light justify-content-start p-2">
<p className="m-0">{data?.description}</p>
</div>
<div className="row ">
<div className="col-12 col-md-6 d-flex align-items-center">
<i className="bx bx-sm bx-user me-1"></i>
<span className="fw-semibold">Contact Person:</span>
<span className="ms-2">{data.contactName}</span>
</div>
<div className="col-12 col-md-6 d-flex align-items-center my-4 m-0">
<i className="bx bx-sm bx-envelope me-1"></i>
<span className="fw-semibold">Email:</span>
<span className="ms-2">{data.email}</span>
</div>
<div className="col-12 col-md-6 d-flex align-items-center">
<i className="bx bx-sm bx-mobile me-1"></i>
<span className="fw-semibold">Contact Number:</span>
<span className="ms-2">{data.contactNumber}</span>
</div>
<div className="col-12 d-flex text-wrap align-items-start my-4 m-0">
<i className="bx bx-sm bx-mark me-1"></i>
<span className="fw-semibold">Address:</span>
<span className="ms-2">{data.billingAddress}</span>
</div>
</div>
</div>
<div className="divider text-start">
<div className="divider-text">Organization</div>
</div>
<div className="col-12 d-flex align-items-center">
<i class="bx bx-sm bxs-building"></i>
<span className="fw-semibold">Industry:</span>
<span className="ms-2">{data.industry.name}</span>
</div>
<div className="row ">
{data.taxId && (
<div className="col-12 col-md-6 d-flex align-items-center">
<i className="bx bx-sm bx-id-card me-1"></i>
<span className="fw-semibold">Tax Id:</span>
<span className="ms-2">{data.taxId}</span>
</div>
)}
<div className="col-12 col-md-6 d-flex align-items-center my-4 m-0">
<i className="bx bx-sm bx-group me-1"></i>
<span className="fw-semibold">Organization Size:</span>
<span className="ms-2">{data.organizationSize}</span>
</div>
<div className="col-12 col-md-6 d-flex align-items-center">
<i className="bx bx-sm bxs-calendar"></i>
<span className="fw-semibold">On-Boarding Date:</span>
<span className="ms-2">
{formatUTCToLocalTime(data.onBoardingDate)}
</span>
</div>
<table className="table table-bordered text-center text-nowrap table-responsive my-4">
<tbody>
<tr>
<td colspan="1">
<strong>Status</strong>
</td>
<td colspan="1">
<strong>Active</strong>
</td>
<td colspan="1">
<strong>In-Progress</strong>
</td>
<td colspan="1">
<strong>On Hold</strong>
</td>
<td>
<strong>In-Active</strong>
</td>
<td>
<strong>Completed</strong>
</td>
</tr>
<tr>
<td>
<strong>Projects</strong>
</td>
<td>
<strong>{data?.activeProjects}</strong>
</td>
<td>
<strong>{data?.inProgressProjects}</strong>
</td>
<td>
<strong>{data?.onHoldProjects}</strong>
</td>
<td>
<strong>{data?.inActiveProjects}</strong>
</td>
<td>
<strong>{data?.completedProjects}</strong>
</td>
</tr>
</tbody>
</table>
</div>
<div className="row">
<div className="col-12 col-md-6 d-flex align-items-center">
<i className="bx bx-sm bx-group me-1"></i>
<span className="fw-semibold">Activite Employees:</span>
<span className="ms-2">{data.activeEmployees}</span>
</div>
<div className="col-12 col-md-6 d-flex align-items-center my-4 m-0">
<i className="bx bx-sm bx-group me-1"></i>
<span className="fw-semibold">In-Active Employee:</span>
<span className="ms-2">{data.inActiveEmployees}</span>
</div>
</div>
</div>
);
};
export default Profile;

View File

@ -1,40 +1,86 @@
import { zodResolver } from '@hookform/resolvers/zod'
import React from 'react'
import { FormProvider, useForm, useFormContext } from 'react-hook-form'
import { defaultFilterValues, filterSchema } from './TenantSchema'
import Label from '../common/Label'
import SelectMultiple from '../common/SelectMultiple'
import { useIndustries } from '../../hooks/useTenant'
import { zodResolver } from "@hookform/resolvers/zod";
import React, { useState } from "react";
import { FormProvider, useForm, useFormContext } from "react-hook-form";
import { defaultFilterValues, filterSchema } from "./TenantSchema";
import Label from "../common/Label";
import SelectMultiple from "../common/SelectMultiple";
import { useIndustries } from "../../hooks/useTenant";
import { reference } from "../../utils/constants";
import { DateRangePicker1 } from "../common/DateRangePicker";
import moment from "moment";
const TenantFilterPanel = () => {
const TenantFilterPanel = ({onApply}) => {
const [resetKey, setResetKey] = useState(0);
const method = useForm({
resolver: zodResolver(filterSchema),
defaultValues: defaultFilterValues,
});
const { control, register, handleSubmit, reset, watch } = method;
const { data, isError, isLoading } = useIndustries();
const closePanel = () => {
document.querySelector(".offcanvas.show .btn-close")?.click();
};
const onSubmit = (formData) => {
onApply({
...formData,
startDate: moment.utc(formData.startDate, "DD-MM-YYYY").toISOString(),
endDate: moment.utc(formData.endDate, "DD-MM-YYYY").toISOString(),
})
};
const onClear = () => {
reset(filterSchema);
setResetKey((prev) => prev + 1);
const method = useForm({
resolver:zodResolver(filterSchema),
defaultValues:defaultFilterValues
})
const {control, register, handleSubmit, reset, watch} = method;
const {data,isError,isLoading} = useIndustries()
console.log(data)
const onSubmit =()=>{
}
if(isLoading) return <div className='text-center'>Loading...</div>
closePanel();
};
if (isLoading) return <div className="text-center">Loading...</div>;
return (
<FormProvider {...method}>
<form onsubmit={(handleSubmit(onSubmit))} >
<div className='text-start mb-1'>
<SelectMultiple
name="industryIds"
<form onSubmit={handleSubmit(onSubmit)}>
<div className="text-start mb-1">
<DateRangePicker1
placeholder="DD-MM-YYYY To DD-MM-YYYY"
startField="startDate"
endField="endDate"
resetSignal={resetKey}
/>
<SelectMultiple
name="industryIds"
label="Industries :"
options={data}
labelKey="name"
valueKey="id"
/>
<SelectMultiple
name="references"
label="References :"
options={reference}
labelKey="name"
valueKey="val"
/>
{/* <SelectMultiple
name="references"
label="Industries :"
options={data}
options={reference}
labelKey="name"
valueKey="id"
/>
</div>
</form>
valueKey="val"
/> */}
</div>
<div className="d-flex justify-content-end py-3 gap-2">
<button
type="button"
className="btn btn-secondary btn-xs"
onClick={onClear}
>
Clear
</button>
<button type="submit" className="btn btn-primary btn-xs">
Apply
</button>
</div>
</form>
</FormProvider>
)
}
);
};
export default TenantFilterPanel
export default TenantFilterPanel;

View File

@ -101,7 +101,7 @@ const TenantForm = () => {
<button
type="button"
className={`step-trigger ${isActive ? "active" : ""}`}
// onClick={() => setActiveTab(index)}
onClick={() => setActiveTab(index)}
>
<span className="bs-stepper-circle">
{isCompleted ? (

View File

@ -67,16 +67,16 @@ export const subscriptionDefaultValues = {
export const filterSchema = z.object({
industryIds: z.array(z.string()).optional(),
createdByIds: z.array(z.string()).optional(),
tenantStatusIds: z.array(z.string()).optional(),
// createdByIds: z.array(z.string()).optional(),
// tenantStatusIds: z.array(z.string()).optional(),
references: z.array(z.string()).optional(),
startDate: z.string().optional(),
endDate: z.string().optional(),
});
export const defaultFilterValues = {
industryIds: [],
createdByIds: [],
tenantStatusIds: [],
// createdByIds: [],
// tenantStatusIds: [],
references: [],
startDate: "YYYY-MM-DDTHH:mm:ssZ",
endDate: "YYYY-MM-DDTHH:mm:ssZ",

View File

@ -8,12 +8,12 @@ import { TenantTableSkeleton } from "./TenanatSkeleton";
import { useTenantContext } from "../../pages/Tenant/TenantPage";
import { useNavigate } from "react-router-dom";
const TenantsList = ({searchText,setIsRefetching, setRefetchFn}) => {
const TenantsList = ({filters,searchText,setIsRefetching, setRefetchFn}) => {
const [currentPage, setCurrentPage] = useState(1);
const navigate = useNavigate()
const { data, isLoading, isError, isInitialLoading, error,refetch, isFetching } = useTenants(
currentPage,
{},
filters,
searchText,
);
@ -41,7 +41,7 @@ const TenantsList = ({searchText,setIsRefetching, setRefetchFn}) => {
key: "name",
label: "Organization",
getValue: (t) => (
<div className="d-flex align-items-center py-1" onClick={()=>navigate(`/tenant/${t.id}`)}>
<div className="d-flex align-items-center py-1 cursor-pointer" onClick={()=>navigate(`/tenant/${t.id}`)}>
<IconButton
iconClass="bx bx-sm bx-building"
color="warning"
@ -95,7 +95,6 @@ const TenantsList = ({searchText,setIsRefetching, setRefetchFn}) => {
{t.tenantStatus?.name || "Unknown"}
</span>
),
align:"text-center"
},
];
if (isInitialLoading)

View File

@ -23,6 +23,16 @@ export const useTenants = (pageNumber, filter = {}, searchString = "") => {
});
};
export const useTenantDetails =(id)=>{
return useQuery({
queryKey:["Tenant",id],
queryFn:async()=>{
const response = await TenantRepository.getTenantDetails(id);
return response.data;
},
})
}
export const useIndustries=()=>{
return useQuery({

View File

@ -2,89 +2,55 @@ import React from "react";
import { useParams } from "react-router-dom";
import Breadcrumb from "../../components/common/Breadcrumb";
import Profile from "../../components/Tenanat/profile";
import { useTenantDetails } from "../../hooks/useTenant";
import Organization from "../../components/Tenanat/Organization";
import { ComingSoonPage } from "../Misc/ComingSoonPage";
const TenantDetails = () => {
const { tenantId } = useParams();
const { data, isLoading, isError, error } = useTenantDetails(tenantId);
const tabs = [
{
id: "navs-left-home",
label: "Profile",
icon: "bx bx-user-circle",
iconSize: "bx-sm",
content: <Profile/>,
iconSize: "bx-sm",
content: <Profile data={data} />,
},
{
id: "navs-left-org",
label: "Organization",
icon: "bx bxs-business",
iconSize: "bx-sm", // change to bx-xs, bx-md, bx-lg
content: (
<>
<p>
Icing pastry pudding oat cake. Lemon drops cotton candy caramels cake...
</p>
<p className="mb-0">
Tootsie roll fruitcake cookie. Dessert topping pie...
</p>
</>
),
},
{
id: "navs-left-profile",
label: "Settings",
icon: "bx bx-cog",
id: "navs-left-bill",
label: "Bill",
icon: "bx bx-receipt",
iconSize: "bx-sm",
content: (
<>
<p>
Donut dragée jelly pie halvah. Danish gingerbread bonbon cookie...
</p>
<p className="mb-0">
Jelly-o jelly beans icing pastry cake cake lemon drops...
</p>
</>
<div className="text-center">
<ComingSoonPage />
</div>
),
},
{
id: "navs-left-messages",
label: "Messages",
icon: "bx bx-message-rounded",
iconSize: "bx-sm",
content: (
<>
<p>
Oat cake chupa chups dragée donut toffee. Sweet cotton candy jelly beans...
</p>
<p className="mb-0">
Cake chocolate bar cotton candy apple pie tootsie roll...
</p>
</>
),
},
{
id: "navs-left-bill",
label: "Bill",
icon: "bx bx-receipt",
iconSize: "bx-sm",
content: (
<>
<p>
Oat cake chupa chups dragée donut toffee. Sweet cotton candy jelly beans...
</p>
<p className="mb-0">
Cake chocolate bar cotton candy apple pie tootsie roll...
</p>
</>
<div className="text-center">
<ComingSoonPage />
</div>
),
},
];
if (isLoading) return <div className="my-4">Loading...</div>;
if (isError) return <div>{error.message}</div>;
return (
<div className="container-fluid py-0">
<Breadcrumb
data={[
{ label: "Home", link: "/dashboard" },
{ label: "Tenant", link: "/tenant" },
{ label: "Tenant", link: "/tenants" },
{ label: "Tenant Details", link: null },
]}
/>
@ -95,7 +61,9 @@ const TenantDetails = () => {
<li key={tab.id} className="nav-item">
<button
type="button"
className={`nav-link d-flex align-items-center gap-2 ${index === 0 ? "active" : ""}`}
className={`nav-link d-flex align-items-center gap-2 ${
index === 0 ? "active" : ""
}`}
role="tab"
data-bs-toggle="tab"
data-bs-target={`#${tab.id}`}
@ -113,7 +81,9 @@ const TenantDetails = () => {
{tabs.map((tab, index) => (
<div
key={tab.id}
className={`tab-pane fade ${index === 0 ? "show active" : ""} text-start`}
className={`tab-pane fade ${
index === 0 ? "show active" : ""
} text-start`}
id={tab.id}
>
{tab.content}

View File

@ -29,6 +29,7 @@ const TenantPage = () => {
const [searchText, setSearchText] = useState("");
const [isRefetching,setRefetching] = useState(false)
const [refetchFn, setRefetchFn] = useState(null);
const [filters, setFilter] = useState();
const debouncedSearch = useDebounce(searchText, 500);
const contextValue = {
};
@ -38,9 +39,22 @@ const TenantPage = () => {
// This Hook allow us to right-side bar for filter Tenants
const methods = useForm({
resolver: zodResolver(filterSchema),
defaultValues: defaultFilterValues,
});
const { reset } = methods;
const clearFilter = () => {
setFilter(defaultFilter);
reset();
};
useEffect(() => {
setShowTrigger(true);
setOffcanvasContent("Tenant Filters", <TenantFilterPanel />);
setOffcanvasContent("Tenant Filters", <TenantFilterPanel onApply={setFilter}
clearFilter={clearFilter}/>);
return () => {
setShowTrigger(false);
setOffcanvasContent("", null);
@ -90,7 +104,7 @@ const TenantPage = () => {
</div>
</div>
<TenantsList searchText={debouncedSearch} setIsRefetching={setRefetching}
<TenantsList filters={filters} searchText={debouncedSearch} setIsRefetching={setRefetching}
setRefetchFn={setRefetchFn}
/>
</div>

View File

@ -14,6 +14,7 @@ export const TenantRepository = {
return api.get(`/api/Tenant/list?${params.toString()}`);
},
getTenantDetails:(id)=>api.get(`/api/Tenant/details/${id}`),
getSubscriptionPlan: (freq) =>
api.get(`/api/Tenant/list/subscription-plan?frequency=${freq}`),

View File

@ -71,6 +71,18 @@ export const EXPENSE_MANAGEMENT = "a4e25142-449b-4334-a6e5-22f70e4732d7"
export const CONSTANT_TEXT = {
}
export const reference = [
{ val: "google", name: "Google" },
{ val: "frined", name: "Friend" },
{ val: "advertisement", name: "Advertisement" },
{ val: "root tenant", name: "Root Tenant" },
];
export const orgSize = [
{ val: "50", name: "1-50" },
{ val: "100", name: "51-100" },
{ val: "500", name: "101-500" },
{ val: "600", name: "500+" },
];
export const BASE_URL = process.env.VITE_BASE_URL;
// export const BASE_URL = "https://api.marcoaiot.com";