integrated tenant creation and addsubscription
This commit is contained in:
parent
34b429c28c
commit
fbc53e9ea1
@ -2,10 +2,10 @@ import React, { useState } from "react";
|
||||
import { useFormContext, Controller } from "react-hook-form";
|
||||
import Label from "../common/Label";
|
||||
import DatePicker from "../common/DatePicker";
|
||||
import { useIndustries } from "../../hooks/useTenant";
|
||||
import { useCreateTenant, useIndustries } from "../../hooks/useTenant";
|
||||
import { LogoUpload } from "./LogoUpload";
|
||||
|
||||
const OrganizationInfo = ({ onNext, onPrev,onSubmitTenant }) => {
|
||||
const OrganizationInfo = ({ onNext, onPrev, onSubmitTenant }) => {
|
||||
const { data, isError, isLoading: industryLoading } = useIndustries();
|
||||
const [logoPreview, setLogoPreview] = useState(null);
|
||||
const [logoName, setLogoName] = useState("");
|
||||
@ -18,27 +18,35 @@ const OrganizationInfo = ({ onNext, onPrev,onSubmitTenant }) => {
|
||||
formState: { errors },
|
||||
} = useFormContext();
|
||||
|
||||
const {
|
||||
mutate: CreateTenant,
|
||||
isError: tenantError,
|
||||
error,
|
||||
isPending,
|
||||
} = useCreateTenant(() => onNext());
|
||||
|
||||
const handleNext = async () => {
|
||||
const valid = await trigger([
|
||||
"organizationName",
|
||||
"officeNumber",
|
||||
"domainName",
|
||||
"description",
|
||||
"onBoardingDate",
|
||||
"organizationSize",
|
||||
"taxId",
|
||||
"industryId",
|
||||
"reference",
|
||||
"logoImage",
|
||||
]);
|
||||
const valid = await trigger([
|
||||
"organizationName",
|
||||
"officeNumber",
|
||||
"domainName",
|
||||
"description",
|
||||
"onBoardingDate",
|
||||
"organizationSize",
|
||||
"taxId",
|
||||
"industryId",
|
||||
"reference",
|
||||
"logoImage",
|
||||
]);
|
||||
|
||||
if (valid) {
|
||||
const data = getValues();
|
||||
onSubmitTenant(data);
|
||||
onNext();
|
||||
}
|
||||
};
|
||||
if (valid) {
|
||||
const tenantPayload = getValues();
|
||||
// onSubmitTenant(data);
|
||||
// onNext();
|
||||
|
||||
CreateTenant(tenantPayload);
|
||||
}
|
||||
};
|
||||
|
||||
const Reference = [
|
||||
{ val: "google", name: "Google" },
|
||||
@ -53,7 +61,6 @@ const OrganizationInfo = ({ onNext, onPrev,onSubmitTenant }) => {
|
||||
{ val: "600", name: "500+" },
|
||||
];
|
||||
|
||||
|
||||
return (
|
||||
<div className="row g-2">
|
||||
<div className="col-sm-6">
|
||||
@ -167,7 +174,10 @@ const OrganizationInfo = ({ onNext, onPrev,onSubmitTenant }) => {
|
||||
{...register("organizationSize")}
|
||||
/> */}
|
||||
|
||||
<select className="form-select form-select-sm" {...register("organizationSize")}>
|
||||
<select
|
||||
className="form-select form-select-sm"
|
||||
{...register("organizationSize")}
|
||||
>
|
||||
{orgSize.map((org) => (
|
||||
<option key={org.val} value={org.val}>
|
||||
{org.name}
|
||||
@ -213,7 +223,10 @@ const OrganizationInfo = ({ onNext, onPrev,onSubmitTenant }) => {
|
||||
}`}
|
||||
{...register("reference")}
|
||||
/> */}
|
||||
<select className="form-select form-select-sm" {...register("reference")}>
|
||||
<select
|
||||
className="form-select form-select-sm"
|
||||
{...register("reference")}
|
||||
>
|
||||
{Reference.map((org) => (
|
||||
<option key={org.val} value={org.val}>
|
||||
{org.name}
|
||||
@ -240,25 +253,23 @@ const OrganizationInfo = ({ onNext, onPrev,onSubmitTenant }) => {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="col-sm-12">
|
||||
<Label htmlFor="logImage">Logo Image</Label>
|
||||
|
||||
<LogoUpload
|
||||
preview={logoPreview}
|
||||
setPreview={setLogoPreview}
|
||||
fileName={logoName}
|
||||
setFileName={setLogoName}
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="col-sm-12">
|
||||
<Label htmlFor="logImage">Logo Image</Label>
|
||||
|
||||
<LogoUpload
|
||||
preview={logoPreview}
|
||||
setPreview={setLogoPreview}
|
||||
fileName={logoName}
|
||||
setFileName={setLogoName}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="d-flex justify-content-between mt-3">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm btn-secondary"
|
||||
onClick={onPrev}
|
||||
disabled={isPending}
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
@ -266,8 +277,9 @@ const OrganizationInfo = ({ onNext, onPrev,onSubmitTenant }) => {
|
||||
type="button"
|
||||
className="btn btn-sm btn-primary"
|
||||
onClick={handleNext}
|
||||
disabled={isPending}
|
||||
>
|
||||
Next
|
||||
{isPending ? "Please Wait...":"Submit and Next"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,14 +1,15 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useSubscriptionPlan } from "../../hooks/useTenant";
|
||||
import { useAddSubscription, useSubscriptionPlan } from "../../hooks/useTenant";
|
||||
import SegmentedControl from "./SegmentedControl";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
import { CONSTANT_TEXT } from "../../utils/constants";
|
||||
import Label from "../common/Label";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
const SubScription = ({ onSubmitSubScription }) => {
|
||||
const [frequency, setFrequency] = useState(2);
|
||||
const [selectedPlanId, setSelectedPlanId] = useState(null);
|
||||
|
||||
const selectedTenant = useSelector((store)=>store.globalVariables.currentTenant)
|
||||
const {
|
||||
data: plans = [],
|
||||
isError,
|
||||
@ -23,7 +24,7 @@ const SubScription = ({ onSubmitSubScription }) => {
|
||||
formState: { errors },
|
||||
} = useFormContext();
|
||||
|
||||
|
||||
const {mutate:AddSubScription,isPending,error} = useAddSubscription()
|
||||
const handleSubscriptionSubmit = async () => {
|
||||
const isValid = await trigger([
|
||||
"planId",
|
||||
@ -36,7 +37,9 @@ const SubScription = ({ onSubmitSubScription }) => {
|
||||
|
||||
if (isValid) {
|
||||
const payload = getValues();
|
||||
onSubmitSubScription(payload);
|
||||
// onSubmitSubScription(payload);
|
||||
const subscriptionPayload = {...payload,tenantId:selectedTenant.id}
|
||||
AddSubScription(subscriptionPayload)
|
||||
}
|
||||
};
|
||||
|
||||
@ -62,7 +65,7 @@ const SubScription = ({ onSubmitSubScription }) => {
|
||||
<div key={plan.id} className="col-md-4">
|
||||
<div
|
||||
className={`card h-100 shadow-none border-1 cursor-pointer ${
|
||||
isSelected ? "border-primary border-2" : ""
|
||||
isSelected ? "border-primary border-1 shadow-md" : ""
|
||||
}`}
|
||||
onClick={() => handlePlanSelection(plan)}
|
||||
>
|
||||
@ -83,11 +86,11 @@ const SubScription = ({ onSubmitSubScription }) => {
|
||||
|
||||
<ul className="list-unstyled d-flex gap-4 flex-wrap mb-2">
|
||||
<li className="d-flex align-items-center">
|
||||
<i className="bx bx-check-double text-success me-1"></i>
|
||||
<i className="bx bx-group me-1"></i>
|
||||
Max Users {plan.maxUser}
|
||||
</li>
|
||||
<li className="d-flex align-items-center">
|
||||
<i className="bx bx-check-double text-success me-2"></i>
|
||||
<i className="bx bx-server me-1"></i>
|
||||
Storage {plan.maxStorage} MB
|
||||
</li>
|
||||
<li className="d-flex align-items-center">
|
||||
@ -110,11 +113,9 @@ const SubScription = ({ onSubmitSubScription }) => {
|
||||
className="mb-2 d-flex align-items-center"
|
||||
>
|
||||
<i
|
||||
className={`bx bx-check bx-xs rounded-circle text-white ${
|
||||
mod.enabled ? "bg-success" : "bg-secondary"
|
||||
}`}
|
||||
className={`fa-regular ${mod.enabled ? "fa-circle-check text-success" : "fa-circle-xmark text-danger"} `}
|
||||
></i>
|
||||
<small className="ms-2">{mod.name}</small>
|
||||
<small className="ms-1">{mod.name}</small>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@ -187,10 +188,11 @@ const SubScription = ({ onSubmitSubScription }) => {
|
||||
<div className="d-flex text-center mt-4">
|
||||
<button
|
||||
onClick={handleSubscriptionSubmit}
|
||||
className="btn btn-primary"
|
||||
className="btn btn-sm btn-primary"
|
||||
type="button"
|
||||
disabled={isPending}
|
||||
>
|
||||
Submit
|
||||
{isPending ? "Please Wait...":"Submit"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -59,7 +59,7 @@ const TenantForm = () => {
|
||||
},
|
||||
{
|
||||
name: "Organization",
|
||||
icon: "bx bx-home bx-md",
|
||||
icon: "bx bx-buildings bx-md",
|
||||
subtitle: "Organization Details",
|
||||
component: <OrganizationInfo onNext={handleNext} onSubmitTenant={onSubmitTenant} />,
|
||||
},
|
||||
|
@ -1,6 +1,8 @@
|
||||
import React, { useState } from "react";
|
||||
import { useTenants } from "../../hooks/useTenant";
|
||||
import { ITEMS_PER_PAGE } from "../../utils/constants";
|
||||
import { getTenantStatus } from "../../utils/dateUtils";
|
||||
import IconButton from "../common/IconButton";
|
||||
|
||||
const TenantsList = () => {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
@ -21,17 +23,110 @@ const TenantsList = () => {
|
||||
<h1>Loading...</h1>
|
||||
</div>
|
||||
);
|
||||
if (isError) return <div>{error}</div>;
|
||||
if (isError) return <div>{error.message}</div>;
|
||||
|
||||
const TenantColumns = [
|
||||
{
|
||||
key: "name",
|
||||
label: "Organization",
|
||||
getValue: (t) => (<div className="d-flex align-items-center py-1">
|
||||
<IconButton iconClass="bx bx-sm bx-building" color="warning" size={8}/>{t.name || "N/A"}
|
||||
</div>),
|
||||
align: "text-start",
|
||||
},
|
||||
{
|
||||
key: "domainName",
|
||||
label: "Domain",
|
||||
getValue: (t) =>(
|
||||
<div style={{width:'160px'}} className="text-truncate">
|
||||
<a href={t.domainName} className="text-decoration-none">
|
||||
<i className="bx bx-globe text-primary bx-xs me-2"></i>
|
||||
{t.domainName || "N/A"}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
),
|
||||
align: "text-start",
|
||||
},
|
||||
{
|
||||
key: "contactName",
|
||||
label: "Contact Person",
|
||||
getValue: (t) => (<div className="d-flex align-items-center"><i className="bx bx-sm bx-user me-1"/>{t.contactName || "N/A"}</div>),
|
||||
align: "text-start",
|
||||
},
|
||||
{
|
||||
key: "contactNumber",
|
||||
label: "Contact",
|
||||
getValue: (t) => t.contactNumber || "N/A",
|
||||
isAlwaysVisible: true,
|
||||
},
|
||||
{
|
||||
key: "status",
|
||||
label: "Status",
|
||||
align: "text-center",
|
||||
getValue: (t) => (
|
||||
<span
|
||||
className={`badge ${
|
||||
getTenantStatus(t.tenantStatus?.id) || "secondary"
|
||||
}`}
|
||||
>
|
||||
{t.tenantStatus?.name || "Unknown"}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
return (
|
||||
<div className="card p-2">
|
||||
<div className="text-end">
|
||||
<button className="bt btn-sm btn-primary me-2">
|
||||
<span class="icon-base bx bx-pie-chart-alt me-1"></span>Create Tenant
|
||||
</button>
|
||||
<>
|
||||
|
||||
<div className="card p-2 mt-3">
|
||||
<div className="card-datatable text-nowrap table-responsive">
|
||||
<table className="table border-top dataTable text-nowrap">
|
||||
<thead>
|
||||
<tr>
|
||||
{TenantColumns.map((col) => (
|
||||
<th key={col.key} className="sorting d-table-cell">
|
||||
<div className={col.align}>{col.label}</div>
|
||||
</th>
|
||||
))}
|
||||
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data?.data.length > 0 ? (
|
||||
data.data.map((tenant) => (
|
||||
<tr key={tenant.id}>
|
||||
{TenantColumns.map((col) => (
|
||||
<td
|
||||
key={col.key}
|
||||
className={`d-table-cell px-3 py-2 align-middle ${col.align ?? ""}`}
|
||||
>
|
||||
{col.customRender
|
||||
? col.customRender(tenant)
|
||||
: col.getValue(tenant)}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td
|
||||
colSpan={TenantColumns.length + 1}
|
||||
className="text-center py-4"
|
||||
>
|
||||
No Tenants Found
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
<div class="card-datatable text-nowrap table-responsive"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TenantsList;
|
||||
export default TenantsList
|
@ -15,7 +15,7 @@ const IconButton = ({
|
||||
iconClass, // icon class string like 'bx bx-user'
|
||||
color = "primary",
|
||||
onClick,
|
||||
size = 20,
|
||||
size = 5,
|
||||
radius=null,
|
||||
style = {},
|
||||
...rest
|
||||
@ -31,7 +31,7 @@ const IconButton = ({
|
||||
style={{
|
||||
backgroundColor,
|
||||
color: iconColor,
|
||||
padding: "0.4rem",
|
||||
padding: "0.3rem",
|
||||
margin:'0rem 0.2rem',
|
||||
...style,
|
||||
}}
|
||||
|
@ -2,6 +2,8 @@ import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import { TenantRepository } from "../repositories/TenantRepository";
|
||||
import { MarketRepository } from "../repositories/MarketRepository";
|
||||
import showToast from "../services/toastService";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { setCurrentTenant } from "../slices/globalVariablesSlice";
|
||||
|
||||
export const useTenants = (
|
||||
pageSize,
|
||||
@ -19,7 +21,6 @@ export const useTenants = (
|
||||
return response.data;
|
||||
},
|
||||
keepPreviousData: true,
|
||||
staleTime: 5 * 60 * 1000,
|
||||
});
|
||||
};
|
||||
|
||||
@ -46,7 +47,8 @@ export const useSubscriptionPlan=(freq)=>{
|
||||
|
||||
// ------------Mutation---------------------
|
||||
|
||||
export const usecreateTenant = (onSuccessCallback)=>{
|
||||
export const useCreateTenant = (onSuccessCallback)=>{
|
||||
const dispatch = useDispatch()
|
||||
return useMutation({
|
||||
mutationFn:async(tenantPayload)=>{
|
||||
const res = await TenantRepository.createTenant(tenantPayload);
|
||||
@ -54,12 +56,25 @@ export const usecreateTenant = (onSuccessCallback)=>{
|
||||
},
|
||||
onSuccess:(data,variables)=>{
|
||||
showToast("Tenant Created SuccessFully","success")
|
||||
|
||||
if(onSuccessCallback) onSuccessCallback()
|
||||
dispatch(setCurrentTenant(data))
|
||||
if(onSuccessCallback) onSuccessCallback()
|
||||
},
|
||||
onError:(error)=>{
|
||||
showToast(error.response.message || error.message || `Something went wrong`,"error")
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
export const useAddSubscription =(onSuccessCallback)=>{
|
||||
return useMutation({
|
||||
mutationFn:async(subscriptionPayload)=>{
|
||||
const res = await TenantRepository.createTenant(subscriptionPayload);
|
||||
return res.data;
|
||||
},
|
||||
onSuccess:(data,variables)=>{
|
||||
showToast("Tenant Plan Added SuccessFully","success")
|
||||
if(onSuccessCallback) onSuccessCallback()
|
||||
}
|
||||
})
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
import React from 'react'
|
||||
import Breadcrumb from '../../components/common/Breadcrumb'
|
||||
import TenantsList from '../../components/Tenanat/TenantsList'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
const TenantPage = () => {
|
||||
const navigate = useNavigate()
|
||||
return (
|
||||
<div className='container-fluid'>
|
||||
<Breadcrumb
|
||||
@ -11,6 +13,21 @@ const TenantPage = () => {
|
||||
{ label: "Tenant", link: null },
|
||||
]}
|
||||
/>
|
||||
<div className="card p-2">
|
||||
<div className="text-end">
|
||||
<button
|
||||
type="button"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-offset="0,8"
|
||||
data-bs-placement="top"
|
||||
data-bs-custom-class="tooltip"
|
||||
title="Add New Tenant"
|
||||
className={`p-1 me-2 bg-primary rounded-circle`} onClick={()=>navigate('/tenants/new-tenant')}
|
||||
>
|
||||
<i className="bx bx-plus fs-4 text-white"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TenantsList />
|
||||
|
||||
|
@ -4,11 +4,13 @@ export const TenantRepository = {
|
||||
|
||||
getTenanatList:(pageSize, pageNumber, filter,searchString)=>{
|
||||
const payloadJsonString = JSON.stringify(filter);
|
||||
return api.get(`/api/Tenant/list?pageSize=${pageSize}&pageNumber=${pageNumber}&filter=${payloadJsonString}&searchString=${searchString}`)
|
||||
return api.get(`/api/Tenant/list?pageSize=${pageSize}&pageNumber=${pageNumber}`)
|
||||
},
|
||||
|
||||
getSubscriptionPlan:(freq)=>api.get(`/api/Tenant/list/subscription-plan?frequency=${freq}`),
|
||||
|
||||
createTenant:(data)=>api.post('/api/Tenant/create',data)
|
||||
createTenant:(data)=>api.post('/api/Tenant/create',data),
|
||||
|
||||
addSubscription:(data)=>api.post('/api/Tenant/create',data)
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,8 @@ import { createSlice } from "@reduxjs/toolkit";
|
||||
const globalVariablesSlice = createSlice({
|
||||
name: "globalVariables",
|
||||
initialState: {
|
||||
loginUser:null
|
||||
loginUser:null,
|
||||
currentTenant:null
|
||||
},
|
||||
reducers: {
|
||||
setGlobalVariable: (state, action) => {
|
||||
@ -13,9 +14,12 @@ const globalVariablesSlice = createSlice({
|
||||
setLoginUserPermmisions: ( state, action ) =>
|
||||
{
|
||||
state.loginUser = action.payload
|
||||
},
|
||||
setCurrentTenant:(state,action)=>{
|
||||
state.currentTenant = action.payload
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const { setGlobalVariable,setLoginUserPermmisions } = globalVariablesSlice.actions;
|
||||
export const { setGlobalVariable,setLoginUserPermmisions,setCurrentTenant } = globalVariablesSlice.actions;
|
||||
export default globalVariablesSlice.reducer;
|
||||
|
@ -68,9 +68,11 @@ export const EXPENSE_MANAGEMENT = "a4e25142-449b-4334-a6e5-22f70e4732d7"
|
||||
export const BASIC_PLAN = "08ddd43e-ca62-4cf4-8d7b-569a364307f4"
|
||||
|
||||
export const BASE_URL = process.env.VITE_BASE_URL;
|
||||
|
||||
export const ActiveTenant = "62b05792-5115-4f99-8ff5-e8374859b191"
|
||||
// export const BASE_URL = "https://api.marcoaiot.com";
|
||||
|
||||
|
||||
export const CONSTANT_TEXT = {
|
||||
RenewsubscriptionLabel : " This option; if checked, will renew your productive subscription, if the current plan expires. However, this might prevent you from"
|
||||
RenewsubscriptionLabel : " This option; if checked, will renew your productive subscription, if the current plan expires."
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import moment from "moment";
|
||||
import { ActiveTenant } from "./constants";
|
||||
|
||||
export const getDateDifferenceInDays = (startDate, endDate) => {
|
||||
if (!startDate || !endDate) {
|
||||
@ -82,4 +83,8 @@ export const getCompletionPercentage = (completedWork, plannedWork)=> {
|
||||
const clamped = Math.min(Math.max(percentage, 0), 100);
|
||||
|
||||
return clamped.toFixed(2);
|
||||
}
|
||||
|
||||
export const getTenantStatus =(statusId)=>{
|
||||
return ActiveTenant === statusId ? " bg-label-success":"bg-label-secondary"
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user