SME-Web/src/store/slices/selectedRecipientsSlice.tsx

328 lines
12 KiB
TypeScript

import { createSlice, PayloadAction, createAsyncThunk } from '@reduxjs/toolkit';
import { toast } from 'react-toastify';
import colors from '../../utlity/colorsArr';
import api from '../../config/api';
export enum recipientType {
SIGNER = "Signer",
INPERSONSIGNER = "InPersonSigner",
CC = "CC"
}
export interface Recipient {
id?: string | null;
firstName: string;
lastName: string;
routingOrder: number;
email: string;
hostName: string;
hostEmail: string;
type: recipientType;
color?: string;
allowSenderToEdit: boolean;
allowSenderToDelete: boolean;
roleName: string
provideOwnTags?: boolean;
uuid?: string;
newOne?: boolean;
completed?: boolean;
}
export interface SelectedRecipientInt {
recipients: Recipient[],
signingOrder: boolean,
transactionTemplateToggle: TRANSACTION_TEMPLATE_TOGGLE
}
interface AddRecipientArgs {
recipientsData: Recipient[];
accountId: string;
transactionId: string;
transactionTemplateToggle: TRANSACTION_TEMPLATE_TOGGLE;
}
interface RemoveRecipientAsyncInterface {
recipientId: number | null;
accountId: string;
transactionId: string;
email: string;
transactionTemplateToggle: TRANSACTION_TEMPLATE_TOGGLE;
}
export enum TRANSACTION_TEMPLATE_TOGGLE {
TRANSACTIONS = "transactions",
TEMPLATES = "templates"
}
const initialState: SelectedRecipientInt = {
recipients: [],
signingOrder: false,
transactionTemplateToggle: TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS
};
// 1. Add Recipient Async
export const addRecipientAsync = createAsyncThunk<
Recipient[],
{ recipientsData: Recipient[]; accountId: string; transactionId: string }
>(
'selectedRecipients/addRecipientAsync',
async ({ recipientsData, accountId, transactionId }, { rejectWithValue, getState }) => {
try {
const state = getState() as { selectedRecipients: SelectedRecipientInt };
const toggle = state.selectedRecipients.transactionTemplateToggle;
const updateRecipientsData = recipientsData.filter(recipient => recipient.id != null);
const createRecipientsData = recipientsData.filter(recipient => recipient.id == null);
const basePath = `/api/v1/accounts/${accountId}/${toggle}/${transactionId}/recipients`;
const resUpdate = updateRecipientsData.length
? await api.put(basePath, updateRecipientsData)
: { data: [] };
const resCreate = createRecipientsData.length
? await api.post(basePath, createRecipientsData)
: { data: [] };
return [...resCreate.data, ...resUpdate.data] as Recipient[];
} catch (err: any) {
return rejectWithValue(err.response?.data || "Failed to add recipient");
}
}
);
// 2. Remove Recipient Async
export const removeRecipientAsync = createAsyncThunk<
string | number,
{ recipientId: number | null; accountId: string; transactionId: string; email: string }
>(
'selectedRecipients/removeRecipientAsync',
async ({ recipientId, accountId, transactionId, email }, { rejectWithValue, getState }) => {
try {
const state = getState() as { selectedRecipients: SelectedRecipientInt };
const toggle = state.selectedRecipients.transactionTemplateToggle;
const basePath = `/api/v1/accounts/${accountId}/${toggle}/${transactionId}/recipients`;
// return
if (recipientId) {
await api.delete(`${basePath}/${recipientId}`);
return recipientId;
}
return email;
} catch (err: any) {
return rejectWithValue("Failed to remove recipient");
}
}
);
// 3. Fetch Recipients Async
export const fetchRecipients = createAsyncThunk<
Recipient[],
{ accountId: string; transactionId: string }
>(
'selectedRecipients/fetchRecipients',
async ({ accountId, transactionId }, { rejectWithValue, getState }) => {
try {
const state = getState() as { selectedRecipients: SelectedRecipientInt };
const toggle = state.selectedRecipients.transactionTemplateToggle;
const basePath = `/api/v1/accounts/${accountId}/${toggle}/${transactionId}/recipients`;
const response = await api.get(basePath);
let data = response.data as Recipient[];
// data = data.filter((recipient, indx) => {
// return recipient.type != "CC";
// })
// if (data.length == 0 && window.location.pathname.includes('/review-and-tag')) {
// window.location.href = window.location.href.replace("review-and-tag", "setup-transaction") // ✅ navigate using window
// }
return data as Recipient[];
} catch (err: any) {
return rejectWithValue(err.response?.data || "Failed to fetch recipients");
}
}
);
const selectedRecipientsSlice = createSlice({
name: 'selectedRecipients',
initialState,
reducers: {
addRecipient: (state, action: PayloadAction<Recipient>) => {
if (action.payload.type == recipientType.INPERSONSIGNER) {
state.signingOrder = true;
state.recipients = [action.payload, ...state.recipients];
} else {
state.recipients.push(action.payload);
}
},
resetRecipients: (state) => {
state.recipients = [];
},
toggleToTemplates: (state) => {
state.transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES;
},
setSelectedRecipients: (state, action: PayloadAction<Recipient[]>) => {
for (const [index, recipient] of action.payload.entries()) {
if ((recipient.type == recipientType.INPERSONSIGNER && index > 0)
&& action.payload[index - 1].type != recipientType.INPERSONSIGNER) {
return state;
}
}
state.recipients = action.payload;
},
setSigningOrder: (state, action: PayloadAction<boolean>) => {
for (const recipient of state.recipients) {
if (recipient.type == recipientType.INPERSONSIGNER) {
if (state.signingOrder) return state;
state.signingOrder = true;
return state;
}
}
state.signingOrder = action.payload;
},
removeSelectedRecipient: (state, action: PayloadAction<Recipient>) => {
state.recipients = state.recipients.filter((recipient) => recipient.uuid != action.payload.uuid);
},
removeAllRecipients: (state) => {
state.recipients = [];
},
addColorsToRecipient: (state) => {
for (const [index, recipient] of state.recipients.entries()) {
recipient.color = colors[index];
}
return state;
},
updateRecipientOrder: (state, action: PayloadAction<{ id: string; routingOrder: number }>) => {
const { id, routingOrder } = action.payload;
const recipient = state.recipients.find((r) => r.id == id || r.uuid == id);
if (recipient) {
recipient.routingOrder = routingOrder;
}
},
reorderAllRecipientAscOrder: (state) => {
state.recipients = state.recipients.sort((a, b) => a.routingOrder - b.routingOrder);
},
updateRecipientByIndxAndRo: (state, action: PayloadAction<{ indx1: number; ro1: number, indx2: number; ro2: number }>) => {
const indx1 = action.payload.indx1
const indx2 = action.payload.indx2
const ro1 = action.payload.ro1
const ro2 = action.payload.ro2
const temp = { ...state.recipients[indx1], routingOrder: ro1 };
state.recipients[indx1] = { ...state.recipients[indx2], routingOrder: ro2 };
state.recipients[indx2] = temp;
},
updateRecipientIds: (state, action: PayloadAction<Recipient[]>) => {
for (const recObj of state.recipients) {
for (const recObj2 of action.payload) {
recObj.id = recObj2.id;
}
}
},
updateRecipient(state, action: PayloadAction<Recipient>) {
const payloadUuid = String(action.payload.uuid);
// First try matching by id
let index = state.recipients.findIndex(
r => String(r.id) == payloadUuid
);
// If not found, try matching by uuid
if (index == -1) {
index = state.recipients.findIndex(
r => String(r.uuid) == payloadUuid
);
}
if (index != -1) {
// Merge existing recipient with new values
state.recipients[index] = {
...state.recipients[index],
...action.payload
};
} else {
console.warn('Recipient not found with uuid:', payloadUuid);
}
}
,
resetSelRecipient: () => initialState
},
extraReducers: (builder) => {
builder
.addCase(fetchRecipients.pending, () => { })
.addCase(fetchRecipients.fulfilled, (state, action: PayloadAction<Recipient[]>) => {
const assignedRecipients = action.payload.map((recipient, index) => ({
...recipient,
color: colors[index],
}));
state.recipients = assignedRecipients;
})
.addCase(fetchRecipients.rejected, (state, action) => {
console.error("Failed to fetch recipients:", action.payload);
// toast.error("Failed to fetch recipients", {
// position: "bottom-right",
// autoClose: 5000,
// theme: "light",
// });
})
.addCase(addRecipientAsync.pending, () => { })
.addCase(addRecipientAsync.fulfilled, (state, action: PayloadAction<Recipient[]>) => {
state.recipients = action.payload.map((recipient, index) => ({
...recipient,
color: colors[index % colors.length],
}));
})
.addCase(addRecipientAsync.rejected, (state, action) => {
console.error("Failed to add recipients:", action.payload);
// toast.error("Failed to add recipients", {
// position: "bottom-right",
// autoClose: 5000,
// theme: "light",
// });
})
.addCase(removeRecipientAsync.pending, () => { })
.addCase(removeRecipientAsync.fulfilled, (state, action: PayloadAction<string | number>) => {
state.recipients = state.recipients.filter(
(recipient) => recipient.id != action.payload && recipient.email != action.payload
);
})
.addCase(removeRecipientAsync.rejected, (state, action) => {
console.error("Failed to delete recipient:", action.payload);
// toast.error("Failed to delete recipient", {
// position: "bottom-right",
// autoClose: 5000,
// theme: "light",
// });
});
}
});
export const {
addRecipient,
setSelectedRecipients,
toggleToTemplates,
removeSelectedRecipient,
removeAllRecipients,
setSigningOrder,
addColorsToRecipient,
updateRecipientIds,
resetSelRecipient,
updateRecipientOrder,
updateRecipient,
resetRecipients,
reorderAllRecipientAscOrder,
updateRecipientByIndxAndRo
} = selectedRecipientsSlice.actions;
export default selectedRecipientsSlice.reducer;