153 lines
4.2 KiB
TypeScript
153 lines
4.2 KiB
TypeScript
import { BASE_URL } from "@/constants/config";
|
|
import api from "@/services/axiosClient";
|
|
import { toCamel } from "@/utils/Payments";
|
|
import { createSlice, PayloadAction, createAsyncThunk } from "@reduxjs/toolkit";
|
|
import { AxiosError } from "axios";
|
|
|
|
type LastEmiStatus = "pending" | "completed";
|
|
|
|
export interface MyPlan {
|
|
noOfEmi: number | null;
|
|
totalAmount: number | null;
|
|
downPayment: number | null;
|
|
emiAmount: number | null;
|
|
totalEmi: number | null;
|
|
installmentPaid: number | null;
|
|
currentAmount: number | null;
|
|
}
|
|
|
|
export interface EmiDetails {
|
|
dueAmount: number | null;
|
|
totalAmountPaidInCurrentCycle: number | null;
|
|
dueDate: string | null;
|
|
status: LastEmiStatus | null;
|
|
advanceBalance: number | null;
|
|
pendingCycles: number | null;
|
|
totalPendingInstallments: number | null;
|
|
myPlan: MyPlan;
|
|
}
|
|
|
|
type LoadingState = "idle" | "pending" | "succeeded" | "failed";
|
|
|
|
export interface EmiDetailsState {
|
|
item?: EmiDetails;
|
|
loading: LoadingState;
|
|
error?: string;
|
|
lastFetchedAt?: number;
|
|
}
|
|
|
|
const initialState: EmiDetailsState = {
|
|
item: undefined,
|
|
loading: "idle",
|
|
error: undefined,
|
|
lastFetchedAt: undefined,
|
|
};
|
|
|
|
export interface MyPlanApi {
|
|
no_of_emi: number | null;
|
|
total_amount: number | null;
|
|
down_payment: number | null;
|
|
emi_amount: number | null;
|
|
total_emi: number | null;
|
|
installment_paid: number | null;
|
|
current_amount: number | null;
|
|
}
|
|
|
|
export interface EmiDetailsApi {
|
|
due_amount: number | null;
|
|
total_amount_paid_in_current_cycle: number | null;
|
|
due_date: string | null;
|
|
status: LastEmiStatus | null;
|
|
advance_balance: number | null;
|
|
pending_cycles: number | null;
|
|
total_pending_installments: number | null;
|
|
myPlain: MyPlanApi;
|
|
}
|
|
|
|
export interface EmiDetailsResponse {
|
|
success: boolean;
|
|
data: EmiDetailsApi[];
|
|
}
|
|
|
|
const mapEmiDetailsApiToModel = (apiObj: EmiDetailsApi): EmiDetails => ({
|
|
dueAmount: apiObj.due_amount,
|
|
totalAmountPaidInCurrentCycle: apiObj.total_amount_paid_in_current_cycle,
|
|
dueDate: apiObj.due_date,
|
|
status: apiObj.status,
|
|
advanceBalance: apiObj.advance_balance,
|
|
pendingCycles: apiObj.pending_cycles,
|
|
totalPendingInstallments: apiObj.total_pending_installments,
|
|
myPlan: {
|
|
noOfEmi: apiObj.myPlain.no_of_emi,
|
|
totalAmount: apiObj.myPlain.total_amount,
|
|
downPayment: apiObj.myPlain.down_payment,
|
|
emiAmount: apiObj.myPlain.emi_amount,
|
|
totalEmi: apiObj.myPlain.total_emi,
|
|
installmentPaid: apiObj.myPlain.installment_paid,
|
|
currentAmount: apiObj.myPlain.current_amount,
|
|
},
|
|
});
|
|
|
|
export const fetchEmiDetails = createAsyncThunk<
|
|
EmiDetails,
|
|
void,
|
|
{ rejectValue: string }
|
|
>("emiDetails/fetch", async (_: void, { rejectWithValue }) => {
|
|
try {
|
|
const res = await api.get(`${BASE_URL}}/api/v1/emi-details`, {
|
|
method: "GET",
|
|
headers: { "Content-Type": "application/json" },
|
|
});
|
|
|
|
const json = res.data;
|
|
|
|
const first = json?.data?.[0];
|
|
if (!json?.success || !first) {
|
|
return rejectWithValue("No EMI details found");
|
|
}
|
|
|
|
return mapEmiDetailsApiToModel(first);
|
|
} catch (e: any) {
|
|
const err = e as AxiosError<any>;
|
|
if (err.code === "ERR_CANCELED") return rejectWithValue("Request aborted");
|
|
const msg =
|
|
err.response?.data?.message || err.message || "Something went wrong";
|
|
return rejectWithValue(msg);
|
|
}
|
|
});
|
|
|
|
const emiDetailsSlice = createSlice({
|
|
name: "emiDetails",
|
|
initialState,
|
|
reducers: {
|
|
clearEmiDetails(state) {
|
|
state.item = undefined;
|
|
state.error = undefined;
|
|
state.loading = "idle";
|
|
state.lastFetchedAt = undefined;
|
|
},
|
|
},
|
|
extraReducers: (builder) => {
|
|
builder
|
|
.addCase(fetchEmiDetails.pending, (state) => {
|
|
state.loading = "pending";
|
|
state.error = undefined;
|
|
})
|
|
.addCase(
|
|
fetchEmiDetails.fulfilled,
|
|
(state, action: PayloadAction<EmiDetails>) => {
|
|
state.loading = "succeeded";
|
|
state.item = action.payload;
|
|
state.lastFetchedAt = Date.now();
|
|
}
|
|
)
|
|
.addCase(fetchEmiDetails.rejected, (state, action) => {
|
|
state.loading = "failed";
|
|
state.error = action.payload as string | undefined;
|
|
});
|
|
},
|
|
});
|
|
|
|
export const { clearEmiDetails } = emiDetailsSlice.actions;
|
|
export default emiDetailsSlice.reducer;
|