Compare commits

..

11 Commits

Author SHA1 Message Date
victorAnumudu 183a6ce903 fixed custom update endpoint 2025-09-30 17:49:42 +01:00
ameye e65371e41b Merge branch 'template-current-selection' of MERMS/MermsFirstOffice into master 2025-09-30 08:50:08 +00:00
victorAnumudu 767a900e36 fixed default template selection 2025-09-30 08:31:37 +01:00
CHIEFSOFT\ameye 8f41dcbf35 fix endpoinst 2025-09-29 20:36:24 -04:00
ameye 11289c44fb Merge branch 'subscription-view-component' of MERMS/MermsFirstOffice into master 2025-09-29 21:07:36 +00:00
victorAnumudu 65f4aafc68 add template layout fix 2025-09-29 22:04:52 +01:00
victorAnumudu 79d5005d6b added custom template and template dropdown menu 2025-09-29 21:46:23 +01:00
ameye ba6b3a1dd0 Merge branch 'link-error-fixed' of MERMS/MermsFirstOffice into master 2025-09-29 17:39:35 +00:00
victorAnumudu ec1402f610 link error isued fixed 2025-09-29 18:36:10 +01:00
CHIEFSOFT\ameye 4481dae24e add templates 2025-09-29 11:53:25 -04:00
ameye 68eb0f9abc Merge branch 'account-profile-data' of MERMS/MermsFirstOffice into master 2025-09-29 14:02:20 +00:00
10 changed files with 340 additions and 33 deletions
@@ -14,12 +14,12 @@ import CustomerPaymentsView from "./CustomerPaymentsView";
export default function AccountViewCom() {
const {state:{memberUID}} = useLocation()
const {state} = useLocation()
const navigate = useNavigate()
useEffect(()=>{
if(!memberUID){
navigate(RouteLinks.customerPage, {replace: true})
if(!state?.memberUID){
navigate(RouteLinks.homePage, {replace: true})
}
},[])
@@ -28,7 +28,7 @@ export default function AccountViewCom() {
queryFn: () => {
// const filterData = filter?.type ? {[filter?.type]: filter.id} : {}
const reqData = {
member_uid: memberUID
member_uid: state?.memberUID
// page,
// ...filterData
}
@@ -45,7 +45,7 @@ export default function AccountViewCom() {
return (
<div className='w-full flex flex-col gap-8'>
<BreadcrumbCom title={`Account View [${memberUID}]`} paths={['Dashboard', 'Account View']}/>
<BreadcrumbCom title={`Account View [${state?.memberUID}]`} paths={['Dashboard', 'Account View']}/>
<div className='box bg-white dark:bg-black-box text-black-body dark:text-white-body'>
{isFetching ?
@@ -58,7 +58,7 @@ export default function AccountViewCom() {
<>
<CustomerAccountView accountInfo={account_info} />
<AccountProfileView profile={account_profile} />
<CustomerSubscriptionsView subscriptions={subscriptions} memberUID={memberUID} />
<CustomerSubscriptionsView subscriptions={subscriptions} />
<CustomerPaymentsView payments={payments} />
</>
@@ -3,7 +3,7 @@ import RouteLinks from "../../RouteLinks";
import Icons from "../Icons";
import {Link} from 'react-router-dom'
export default function CustomerSubscriptionsView({subscriptions, memberUID}) {
export default function CustomerSubscriptionsView({subscriptions}) {
return <>
<div className="pb-5">
<div className="flex flex-col gap-1">
@@ -70,7 +70,7 @@ export default function CustomerSubscriptionsView({subscriptions, memberUID}) {
<div className='flex items-center justify-end gap-3 md:gap-4'>
<div
className='p-2 flex justify-center items-center text-slate-500 bg-white-body dark:text-white-body dark:bg-black-body rounded-md'>
<Link to={`/subscription-view/${item?.subscription_uid}`} state={{customerID: item?.id, memberUID}}>
<Link to={`/subscription-view/${item?.subscription_uid}`} state={{customerID: item?.id, subscriptionUID: item?.subscription_uid}}>
<Icons name='eye'/>
</Link>
</div>
+3 -1
View File
@@ -155,7 +155,9 @@ export default function HomeCom() {
<div className='flex items-center justify-end gap-3 md:gap-4'>
<div
className='p-2 flex justify-center items-center text-slate-500 bg-white-body dark:text-white-body dark:bg-black-body rounded-md'>
<Link to={`/subscription-view/${item?.uid}`}>
<Link to={`/subscription-view/${item?.uid}`}
state={{subscriptionUID: item?.uid}}
>
<Icons name='eye'/>
</Link>
</div>
+106
View File
@@ -0,0 +1,106 @@
import {useMutation, useQueryClient} from '@tanstack/react-query'
import {Formik, Form} from 'formik'
import * as Yup from "yup";
import InputText from '../InputText'
import {addCustomTemplate} from '../../services/siteServices'
import queryKeys from '../../services/queryKeys';
const initialValues = {
custom_id: "",
provision_name: "",
};
// To get the validation schema
const validationSchema = Yup.object().shape({
custom_id: Yup.string().required("custom_id is required").min(6, 'must be upto 6 characters').max(25, 'must not exceed 25 characters'),
provision_name: Yup.string().required("provision_name is required").min(6, 'must be upto 6 characters').max(200, 'must not exceed 200 characters'),
});
export default function AddTemplate() {
const queryClient = useQueryClient()
const customTemplate = useMutation({
mutationFn: (fields) => {
if (!fields.custom_id || !fields.provision_name) {
throw new Error('Please provide all fields marked *')
}
return addCustomTemplate(fields)
},
onSuccess: () => {
queryClient.refetchQueries({
queryKey: [...queryKeys.custom_template],
// type: 'active',
// exact: true,
})
},
})
//FUNCTION TO HANDLE ADD TEMPLATE
const handleSubmit = (values, helper) => {
customTemplate.mutate(values)
};
return (
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={handleSubmit}
>
{(props) => (
<Form>
<div
className='flex flex-col w-full bg-white rounded-xl p-16 sm:px-20 sm:py-16 shadow'>
<div className='w-full flex flex-col gap-4'>
<div className='relative text-input flex flex-col sm:flex-row gap-2 sm:items-center'>
<label className={`text-base min-w-36 text-end sm:text-left ${(props.errors.custom_id && props.touched.custom_id) && 'text-red-500'}`}>
Custom ID
</label>
<InputText
id='custom_id'
placeholder='Custom ID'
name='custom_id'
value={props.values.custom_id}
handleChange={props.handleChange}
/>
</div>
<div className='relative text-input flex flex-col sm:flex-row gap-2 sm:items-center'>
<label className={`text-base min-w-36 text-end sm:text-left ${(props.errors.provision_name && props.touched.provision_name) && 'text-red-500'}`}>
Provision Name
</label>
<InputText
id='provision_name'
placeholder='Provision Name'
name='provision_name'
value={props.values.provision_name}
handleChange={props.handleChange}
/>
</div>
<div className='h-10 my-5 text-end'>
<button type='submit' disabled={customTemplate.isPending}
className='px-4 h-full bg-primary text-white font-bold rounded-md'>{customTemplate.isPending ? 'loading...' : 'Add'}</button>
</div>
{customTemplate.error &&
<>
<div className="w-full text-center">
<p className='text-red-500 text-sm'>{customTemplate.error.message}</p>
</div>
</>
}
{customTemplate.isSuccess &&
<>
<div className="w-full text-center">
<p className='text-emerald-500 text-sm'>{'Template Added'}</p>
</div>
</>
}
</div>
</div>
</Form>
)}
</Formik>
)
}
+9 -2
View File
@@ -7,6 +7,7 @@ import TablePaginatedWrapper from '../tableWrapper/TablePaginatedWrapper'
import Icons from '../Icons'
import { getCustomTemplate } from '../../services/siteServices'
import getDateTimeFromDateString from '../../helpers/getDateTimeFromDateString'
import AddTemplate from './AddTemplate'
export default function CustomTemplates() {
@@ -50,7 +51,12 @@ export default function CustomTemplates() {
return (
<div className='w-full flex flex-col gap-8'>
<BreadcrumbCom title='Custom Templates' paths={['Dashboard', 'Custom Templates']} />
<div className='box bg-white dark:bg-black-box text-black-body dark:text-white-body' style={{backgroundColor: 'aliceblue'}}>
<div className='mb-3'>
<b>Add New Custom Template</b>
</div>
<AddTemplate />
</div>
<div className='box bg-white dark:bg-black-box text-black-body dark:text-white-body'>
{ isError ?
<p className='text-red-500'>{error?.message}</p>
@@ -62,7 +68,8 @@ export default function CustomTemplates() {
<div className='w-full sm:max-w-48'>
<select name='type' value={filter?.type} className='h-10 w-full p-2 rounded-md' onChange={handleFilter}>
<option value=''>All</option>
<option value='name'>Name</option>
<option value='custom_id'>Custom ID</option>
<option value='provision_name'>Provision Name</option>
</select>
</div>
<div className='w-full sm:max-w-48'>
@@ -81,9 +81,6 @@ export default function ProductTemplates() {
<th scope="col" className="px-2 py-2">
Product ID
</th>
<th scope="col" className="px-2">
Name
</th>
<th scope="col" className="px-2">
Provision Name
</th>
@@ -102,11 +99,6 @@ export default function ProductTemplates() {
</div>
</div>
</td>
<td className="px-2">
<div className="text-left">
<div className="text-base font-semibold">{item?.name}</div>
</div>
</td>
<td className="px-2">
<div className="text-left">
<div className="text-base font-semibold">{item?.provision_name}</div>
@@ -1,32 +1,201 @@
import {useLocation, useNavigate, Link} from 'react-router-dom'
import { useQuery, useMutation } from '@tanstack/react-query'
import { FaCaretDown } from "react-icons/fa";
import BreadcrumbCom from '../breadcrumb/BreadcrumbCom'
import { useEffect } from 'react';
import {useEffect, useState} from 'react';
import RouteLinks from '../../RouteLinks';
import { getSubscriptionsView, updateTemplate, updateCustomTemplate } from '../../services/siteServices'
import queryKeys from '../../services/queryKeys'
import getDateTimeFromDateString from '../../helpers/getDateTimeFromDateString';
export default function SubscriptionViewCom() {
const subscriptionUID = "63554d40-9ba1-4afe-80c2-ca147236f7ee";
const {state:{memberUID}} = useLocation()
const {state} = useLocation()
const navigate = useNavigate()
useEffect(()=>{
if(!memberUID){
navigate(`/account-view/${memberUID}`, {replace: true, state:{memberUID}})
const [reqStatus, setReqStatus] = useState({loading: false, type: '', error: false, success: false})
const [values, setValues] = useState({custom_id: '', template_uid: ''})
const handleValueChange = ({target:{name, value}}) => {
if(name == 'custom_template'){
setValues(prev => ({...prev, custom_id: value}))
}else if (name == 'template') {
setValues(prev => ({...prev, template_uid: value}))
}else{
setValues(prev => ({...prev}))
}
},[])
}
useEffect(() => {
if (!state?.subscriptionUID) {
navigate(RouteLinks.homePage, {replace: true})
}
}, [])
const {data, isFetching, isError, error} = useQuery({
queryKey: queryKeys.subscriptions_view,
queryFn: () => {
// const filterData = filter?.type ? {[filter?.type]: filter.id} : {}
const reqData = {
subscription_uid: state?.subscriptionUID
// page,
// ...filterData
}
return getSubscriptionsView(reqData)
},
staleTime: 0 //0 mins
})
const subscriptionViewData = data?.data // ACCOUNT VIEW DATA
const customTemplates = subscriptionViewData?.available_custom_templates
const availableTemplates = subscriptionViewData?.available_templates
const selectedSubscription = subscriptionViewData?.subscription
const currentCustomTem = subscriptionViewData?.available_custom_templates?.filter(item => item?.custom_id == subscriptionViewData?.subscription?.custom_template)[0]?.custom_id
const currentTemplate = subscriptionViewData?.available_templates?.filter(item => item?.template_uid == subscriptionViewData?.subscription?.product_template)[0]?.template_uid
// // console.log('subscriptionViewData', subscriptionViewData, currentCustomTem, currentTemplate)
// useEffect(()=>{
// if(data){
// const currentCustomTem = subscriptionViewData?.available_custom_templates?.filter(item => item?.custom_id == subscriptionViewData?.subscription?.custom_template)[0]?.custom_id
// const currentTemplate = subscriptionViewData?.available_templates?.filter(item => item?.template_uid == subscriptionViewData?.subscription?.product_template)[0]?.template_uid
// setValues({custom_id: currentCustomTem || '', template_uid: currentTemplate || ''})
// }
// },[data])
const templateUpdate = useMutation({
mutationFn: (fields) => {
return updateTemplate(fields)
},
// onError: (error) => {
// },
// onSuccess: (res) => {
// },
onSettled: () => {
setTimeout(()=>{
templateUpdate.reset()
}, 3000)
}
})
const customTemplateUpdate = useMutation({
mutationFn: (fields) => {
return updateCustomTemplate(fields)
},
// onError: (error) => {
// },
// onSuccess: (res) => {
// },
onSettled: () => {
setTimeout(()=>{
customTemplateUpdate.reset()
}, 3000)
}
})
const handleUpdateTemplate = () => {
const reqData = {
Subscrtiption_uid: state?.subscriptionUID,
template_uid: values.template_uid
}
templateUpdate.mutate(reqData)
}
const handleUpdateCustomTemplate = () => {
const reqData = {
Subscrtiption_uid: state?.subscriptionUID,
custom_id: values.custom_id
}
customTemplateUpdate.mutate(reqData)
}
return (
<div className='w-full flex flex-col gap-8'>
<BreadcrumbCom title={`Subscription View [${memberUID}]`} paths={['Dashboard', 'Subscription View']}/>
<BreadcrumbCom title={`Subscription View [${state?.subscriptionUID}]`} paths={['Dashboard', 'Subscription View']}/>
<div className='box bg-white dark:bg-black-box text-black-body dark:text-white-body'>
{isFetching ?
<>
<p className='text-slate-800'>Loading...</p>
</>
: isError ?
<p className='text-red-500'>{error.message}</p>
:
<>
<div className='w-full box bg-white dark:bg-black-box text-black-body dark:text-white-body overflow-x-auto'>
<table className="py-2 w-full text-sm bg-[aliceblue] dark:bg-transparent rounded-[10px]">
<tbody>
<tr className="py-2 border-t border-dashed border-slate-300">
<td className="px-2 py-2">
<div className="text-left">
<div className="text-base font-semibold">{getDateTimeFromDateString(selectedSubscription?.added)}</div>
</div>
</td>
<td className="px-2">
<div className="text-left">
<div className="text-base font-semibold">{selectedSubscription?.product_id}</div>
</div>
</td>
<td className="px-2">
<div className="text-left">
<div className="text-base font-semibold">{selectedSubscription?.internal_url}
<br /><span>Template :</span> {selectedSubscription?.product_template}
<br /><span>Custom :</span> {selectedSubscription?.custom_template}
</div>
</div>
</td>
<td className="px-2">
<div className="text-right">
<div className="text-base font-semibold">{selectedSubscription?.status}</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<>
Show all here
</>
<div className='box bg-white dark:bg-black-box text-black-body dark:text-white-body'>
<div className='w-full'>
<label className='font-medium'>Assign Template</label>
<div className='flex flex-col xs:flex-row md:items-center gap-2'>
<div className='w-full h-10 relative overflow-hidden rounded-md'>
<select name='template' value={currentTemplate || values.template_uid} onChange={handleValueChange} className='w-full h-full p-2 appearance-none dark:bg-transparent border-0 dark:border-1 border-white ring-0 outline-none'>
<option value=''>None</option>
{availableTemplates && availableTemplates.map(item => (
<option key={item?.template_uid} value={item?.template_uid}>{`${item?.product_id}-${item?.provision_name}`}</option>
))}
</select>
<FaCaretDown className='text-base absolute top-1/2 -translate-y-1/2 right-2' />
</div>
<button name='template' onClick={handleUpdateTemplate} disabled={(templateUpdate.isPending || !values.template_uid)} className={`rounded-md p-2 bg-primary text-white text-center ${(templateUpdate.isPending || !values.template_uid) && 'opacity-50'}`}>Update</button>
</div>
<p className={`p-2 mt-4 ${templateUpdate.isSuccess ? 'text-emerald-500' : 'text-red-500'}`}>{templateUpdate.isSuccess ? 'Template updated' : templateUpdate.isSuccess ? 'Unable to complete request, try again' : ''}</p>
</div>
</div>
</div>
<div className='box bg-white dark:bg-black-box text-black-body dark:text-white-body'>
<div className='w-full'>
<label className='font-medium'>Assign Custom Template</label>
<div className='flex flex-col xs:flex-row md:items-center gap-2'>
<div className='w-full h-10 relative overflow-hidden rounded-md'>
<select name='custom_template' value={currentCustomTem || values.custom_id} onChange={handleValueChange} className='w-full h-full p-2 appearance-none dark:bg-transparent border-0 dark:border-1 border-white ring-0 outline-none'>
<option value=''>None</option>
{customTemplates && customTemplates.map(item => (
<option key={item?.custom_id} value={item?.custom_id}>{`${item?.custom_id}-${item?.provision_name}`}</option>
))}
</select>
<FaCaretDown className='text-base absolute top-1/2 -translate-y-1/2 right-2' />
</div>
<button name='custom_template' onClick={handleUpdateCustomTemplate} disabled={(customTemplateUpdate.isPending || !values.custom_id)} className={`rounded-md p-2 bg-primary text-white text-center ${(customTemplateUpdate.isPending || !values.custom_id) && 'opacity-50'}`}>Update</button>
</div>
<p className={`p-2 mt-4 ${customTemplateUpdate.isSuccess ? 'text-emerald-500' : 'text-red-500'}`}>{customTemplateUpdate.isSuccess ? 'Custom Template updated' : customTemplateUpdate.isSuccess ? 'Unable to complete request, try again' : ''}</p>
</div>
</div>
</>
}
</div>
)
}
@@ -146,7 +146,7 @@ export default function SubscriptionsCom() {
<div
className='p-2 flex justify-center items-center text-slate-500 bg-white-body dark:text-white-body dark:bg-black-body rounded-md'>
<Link to={`/subscription-view/${item?.subscription_uid}`}
// state={{customerID: item?.id, memberUID: item?.member_uid}}
state={{customerID: item?.id, subscriptionUID: item?.subscription_uid}}
>
<Icons name='eye'/>
</Link>
+1
View File
@@ -20,6 +20,7 @@ const queryKeys = {
products_template: ['products_template'],
custom_template: ['custom_template'],
account_view: ['account_view'],
subscriptions_view: ['subscriptions_view'],
users_admin: ['users_admin'],
}
+30
View File
@@ -104,6 +104,30 @@ export const getProductsTemplate = (reqData) => {
return getAuxEnd(`/products-templates`, postData)
}
// FUNCTION TO UPDATE TEMPLATE
export const updateTemplate = (reqData) => {
let postData = {
...reqData
}
return postAuxEnd('/template/set-template', postData, false)
}
// FUNCTION TO UPDATE CUSTOM TEMPLATE
export const updateCustomTemplate = (reqData) => {
let postData = {
...reqData
}
return postAuxEnd('/template/set-custom', postData, false)
}
// FUNCTION TO ADD CUSTOM TEMPLATE
export const addCustomTemplate = (reqData) => {
let postData = {
...reqData
}
return postAuxEnd('/template/custom-add', postData, false)
}
// FUNCTION TO GET CUSTOM TEMPLATE DATA
export const getCustomTemplate = (reqData) => {
const postData = { ...reqData }
@@ -116,6 +140,12 @@ export const getAccountView = (reqData) => {
return getAuxEnd(`/account-view`, postData)
}
// FUNCTION TO GET SUBSCRIPTIONS VIEW DATA
export const getSubscriptionsView = (reqData) => {
const postData = { ...reqData }
return getAuxEnd(`/subscription-view`, postData)
}
// FUNCTION TO GET LOANS TABLE
export const getLoans = (reqData) => {