Initial commit

This commit is contained in:
2025-10-04 18:22:24 +03:00
commit 2852c2c054
291 changed files with 38109 additions and 0 deletions

View File

@@ -0,0 +1,105 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { baseApi } from "redux/api/apiSlice";
import { AppThunk } from "redux/store";
import { ACCESS_TOKEN, USER_NAME } from "utils/constants";
import { LoginResponseType, UserType } from "utils/types/appTypes";
export type AuthState = {
user: UserType | undefined;
isActivated: boolean;
loading: boolean;
loginFailed: boolean;
expiredToken: boolean;
error: string;
token: string;
loaded: boolean;
};
const initialState: AuthState = {
user: undefined,
isActivated: !!localStorage.getItem(ACCESS_TOKEN),
loaded: false,
loading: false,
loginFailed: false,
expiredToken: false,
error: "",
token: localStorage.getItem(ACCESS_TOKEN) || "",
};
export const authSlice = createSlice({
name: "auth",
initialState,
reducers: {
resetState: () => initialState,
initialLoadStart(state) {
state.loading = true;
state.isActivated = false;
},
initialLoadSuccess(state) {
state.loading = false;
state.isActivated = true;
},
loginStart(state) {
state.loading = true;
state.loginFailed = false;
state.loaded = false;
},
loginSuccess(state, action: PayloadAction<LoginResponseType>) {
if (action.payload.token) {
localStorage.setItem(ACCESS_TOKEN, action.payload.token);
state.token = action.payload.token;
if (state.user?.username)
localStorage.setItem(USER_NAME, state.user.username);
state.isActivated = true;
state.loading = false;
state.loginFailed = false;
state.expiredToken = false;
state.error = "";
state.loaded = true;
}
},
logout: (state) => {
localStorage.removeItem(ACCESS_TOKEN);
state.isActivated = false;
state.token = "";
state.user = undefined;
},
failedWithError(state, action: PayloadAction<string>) {
state.loading = false;
state.loginFailed = true;
state.error = action.payload;
},
resetLoadedStatus(state) {
state.loaded = false;
},
setUser(state, action: PayloadAction<any>) {
state.user = action.payload;
},
tokenExpired(state) {
state.expiredToken = true;
},
},
});
export const {
resetState,
initialLoadStart,
initialLoadSuccess,
loginStart,
loginSuccess,
failedWithError,
tokenExpired,
logout,
setUser,
resetLoadedStatus,
} = authSlice.actions;
export default authSlice.reducer;
export const logoutThunk = (): AppThunk => (dispatch) => {
if (localStorage.getItem(ACCESS_TOKEN)) {
localStorage.clear();
dispatch(baseApi.util.resetApiState());
dispatch(logout());
}
};

View File

@@ -0,0 +1,22 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
export type AppErrorState = {
errorCode: number;
};
const initialState: AppErrorState = {
errorCode: 200,
};
export const appErrorSlice = createSlice({
name: "appError",
initialState,
reducers: {
setErrorCode(state, action: PayloadAction<number>) {
state.errorCode = action.payload;
// if(errorCode === 401 && store.getState().auth.isActivated)dispach()
},
},
});
export const { setErrorCode } = appErrorSlice.actions;
export default appErrorSlice.reducer;

View File

@@ -0,0 +1,63 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import dayjs from "dayjs";
import "dayjs/locale/ar";
import LocalizedFormat from "dayjs/plugin/localizedFormat";
import preParsePostFormat from "dayjs/plugin/preParsePostFormat";
import { AppThunk } from "redux/store";
import { DEFAULT_LANGUAGE } from "utils/constants";
dayjs.extend(LocalizedFormat);
dayjs.extend(preParsePostFormat); // for arabic numbers
export type LocaleState = {
locale: string;
isRTL: boolean;
};
const initialState: LocaleState = {
locale:
localStorage.getItem(DEFAULT_LANGUAGE) ||
import.meta.env.VITE_DEFAULT_LANGUAGE,
isRTL:
(localStorage.getItem(DEFAULT_LANGUAGE) ||
import.meta.env.VITE_DEFAULT_LANGUAGE) === "ar",
};
function setDateLocale(appLocale: string) {
switch (appLocale) {
case "ar":
dayjs.locale("ar");
break;
case "en":
dayjs.locale("en");
break;
default:
dayjs.locale("en");
break;
}
}
export const localeSlice = createSlice({
name: "locale",
initialState,
reducers: {
setLocale(state, action: PayloadAction<string>) {
state.locale = action.payload;
state.isRTL = action.payload === "ar";
},
},
});
export const { setLocale } = localeSlice.actions;
export default localeSlice.reducer;
export const setLocalesThunk =
(newLocale?: string): AppThunk =>
async (dispatch) => {
const storedLang = localStorage.getItem(DEFAULT_LANGUAGE);
const lang =
newLocale || storedLang || import.meta.env.VITE_DEFAULT_LANGUAGE;
if (storedLang !== lang) localStorage.setItem(DEFAULT_LANGUAGE, lang);
setDateLocale(lang);
dispatch(setLocale(lang));
};

View File

@@ -0,0 +1,108 @@
import { TaxType } from "modules/system/taxes/types";
import { ItemType, OrderType } from "pages/pos/orders/types";
/**
* To aggregate all the items to make sure to show totals over all the order not only one order
* @param activeTab
* @param orders
* @param items
* @returns
*/
export function prepareTheCompletedList(
activeTab: string,
orders: OrderType[],
items: ItemType[]
) {
let theCompletedList: ItemType[] | undefined = [];
if (activeTab == "0")
theCompletedList = [...(theCompletedList || []), ...(items || [])];
if (activeTab)
orders.forEach((o) => {
if (o.id == activeTab)
theCompletedList = [...(theCompletedList || []), ...(items || [])];
else theCompletedList = [...(theCompletedList || []), ...(o.items || [])];
});
else {
theCompletedList = items;
}
return theCompletedList;
}
export function calculateSubTotal(items: Array<ItemType>) {
let subTotalAmount = 0;
items?.forEach((i) => {
subTotalAmount += Number(i.unit_price) * (i?.quantity || 0);
});
return subTotalAmount;
}
export function calculateDiscount(
subTotalAmount: number,
discount: {
type: "PERCENTAGE" | "FIXED";
value: number;
}
) {
let discountAmount = 0;
if (discount.type === "PERCENTAGE") {
discountAmount += (Number(discount.value) / 100) * subTotalAmount;
}
if (discount.type === "FIXED") {
discountAmount += Number(discount.value);
}
return Math.ceil(discountAmount / 100) * 100;
}
export function calculateTax(subTotalAmount: number, taxes: Array<TaxType>) {
const newTaxes = [...taxes];
let taxesAmount = 0;
newTaxes
.sort((a, b) => (a.from_tax?.id || 0) - (b.from_tax?.id || 0))
.forEach((tax: TaxType) => {
taxesAmount +=
Math.ceil(calculateTaxAmount(subTotalAmount, tax) / 100) * 100;
});
return taxesAmount;
}
export function calculateSpecificTax(
subTotalAmount: number,
taxes: Array<TaxType>,
tax: TaxType
) {
let taxesAmount = 0;
const newTaxes = [...taxes];
newTaxes
.sort((a, b) => (a.from_tax?.id || 0) - (b.from_tax?.id || 0))
.forEach((t: TaxType) => {
if (t.id === tax.id)
taxesAmount +=
Math.ceil(calculateTaxAmount(subTotalAmount, tax) / 100) * 100;
});
return taxesAmount;
}
export function calculateTaxAmount(amount: number, tax: TaxType) {
const value = tax.value;
let total = amount;
if (tax.from_tax !== null) {
total = calculateTaxAmount(total, tax.from_tax);
}
total = tax.type === "FIXED" ? value : (value * total) / 100;
return total;
}
export function calculateGrandTotalAmount(
subTotalAmount: number,
taxAmount: number,
discountAmount: number
) {
return subTotalAmount + taxAmount - discountAmount;
}

View File

@@ -0,0 +1,311 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { RootState } from "redux/store";
import { CartItem } from "utils/types/appTypes";
interface LocationData {
lat: number;
lng: number;
address: string;
}
export interface RoomDetailsType {
roomNo: string;
floorNo: string;
guestName: string;
note: string;
}
export interface OfficeDetailsType {
officeNo: string;
floorNo: string;
note: string;
company: string;
contactPerson: string;
phone: string;
}
export interface GiftDetailsType {
receiverName: string;
receiverPhone: string;
message: string;
senderName: string;
senderPhone: string;
isSecret: boolean;
}
interface CartState {
items: CartItem[];
tmp: unknown;
specialRequest: string;
location: LocationData | null;
roomDetails: RoomDetailsType | null;
officeDetails: OfficeDetailsType | null;
giftDetails: GiftDetailsType | null;
coupon: string;
tip: string;
tables: string[];
estimateTime: Date;
estimateTimeDate: Date;
estimateTimeTime: string;
collectionMethod: string;
}
// localStorage keys
const CART_STORAGE_KEYS = {
ITEMS: 'fascano_cart_items',
SPECIAL_REQUEST: 'fascano_special_request',
COUPON: 'fascano_coupon',
TIP: 'fascano_tip',
TABLES: 'fascano_tables',
LOCATION: 'fascano_location',
ROOM_DETAILS: 'fascano_room_details',
OFFICE_DETAILS: 'fascano_office_details',
GIFT_DETAILS: 'fascano_gift_details',
ESTIMATE_TIME: 'fascano_estimate_time',
ESTIMATE_TIME_DATE: 'fascano_estimate_time_date',
ESTIMATE_TIME_TIME: 'fascano_estimate_time_time',
COLLECTION_METHOD: 'fascano_collection_method',
} as const;
// Utility functions for localStorage
const getFromLocalStorage = <T>(key: string, defaultValue: T): T => {
if (typeof window === 'undefined') return defaultValue;
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : defaultValue;
} catch (error) {
console.warn(`Error reading ${key} from localStorage:`, error);
return defaultValue;
}
};
const initialState: CartState = {
items: getFromLocalStorage(CART_STORAGE_KEYS.ITEMS, []),
tmp: null,
specialRequest: getFromLocalStorage(CART_STORAGE_KEYS.SPECIAL_REQUEST, ""),
location: getFromLocalStorage(CART_STORAGE_KEYS.LOCATION, null),
roomDetails: getFromLocalStorage(CART_STORAGE_KEYS.ROOM_DETAILS, null),
officeDetails: getFromLocalStorage(CART_STORAGE_KEYS.OFFICE_DETAILS, null),
giftDetails: getFromLocalStorage(CART_STORAGE_KEYS.GIFT_DETAILS, null),
coupon: getFromLocalStorage(CART_STORAGE_KEYS.COUPON, ""),
tip: getFromLocalStorage(CART_STORAGE_KEYS.TIP, ""),
tables: getFromLocalStorage(CART_STORAGE_KEYS.TABLES, []),
estimateTime: new Date(getFromLocalStorage(CART_STORAGE_KEYS.ESTIMATE_TIME, new Date().toISOString())),
estimateTimeDate: new Date(getFromLocalStorage(CART_STORAGE_KEYS.ESTIMATE_TIME_DATE, new Date().toISOString())),
estimateTimeTime: getFromLocalStorage(CART_STORAGE_KEYS.ESTIMATE_TIME_TIME, ""),
collectionMethod: getFromLocalStorage(CART_STORAGE_KEYS.COLLECTION_METHOD, ""),
};
const orderSlice = createSlice({
name: "order",
initialState,
reducers: {
reset() {
return initialState;
},
addItem(state, action: PayloadAction<{ item: Omit<CartItem, "quantity">; quantity: number }>) {
const { item, quantity } = action.payload;
const existingItem = state.items.find((i) => i.id === item.id);
if (existingItem) {
state.items = state.items.map((i) =>
i.id === item.id ? { ...i, quantity: i.quantity + quantity } : i
);
} else {
state.items = [...state.items, { ...item, quantity }];
}
// Sync to localStorage
if (typeof window !== 'undefined') {
localStorage.setItem(CART_STORAGE_KEYS.ITEMS, JSON.stringify(state.items));
}
},
updateQuantity(state, action: PayloadAction<{ id: number | string; quantity: number }>) {
const { id, quantity } = action.payload;
state.items = state.items.map((item) =>
item.id === id ? { ...item, quantity } : item
);
// Sync to localStorage
if (typeof window !== 'undefined') {
localStorage.setItem(CART_STORAGE_KEYS.ITEMS, JSON.stringify(state.items));
}
},
removeItem(state, action: PayloadAction<number | string>) {
state.items = state.items.filter((item) => item.id !== action.payload);
// Sync to localStorage
if (typeof window !== 'undefined') {
localStorage.setItem(CART_STORAGE_KEYS.ITEMS, JSON.stringify(state.items));
}
},
clearCart(state) {
state.items = [];
state.specialRequest = "";
state.coupon = "";
state.tip = "";
state.tables = [];
state.location = null;
state.roomDetails = null;
state.officeDetails = null;
state.giftDetails = null;
state.estimateTime = new Date();
state.estimateTimeDate = new Date();
state.estimateTimeTime = "";
state.collectionMethod = "";
// Clear all cart data from localStorage
if (typeof window !== 'undefined') {
Object.values(CART_STORAGE_KEYS).forEach(key => {
localStorage.removeItem(key);
});
}
},
updateSpecialRequest(state, action: PayloadAction<string>) {
state.specialRequest = action.payload;
// Sync to localStorage
if (typeof window !== 'undefined') {
localStorage.setItem(CART_STORAGE_KEYS.SPECIAL_REQUEST, JSON.stringify(state.specialRequest));
}
},
clearSpecialRequest(state) {
state.specialRequest = "";
// Sync to localStorage
if (typeof window !== 'undefined') {
localStorage.removeItem(CART_STORAGE_KEYS.SPECIAL_REQUEST);
}
},
updateCoupon(state, action: PayloadAction<string>) {
state.coupon = action.payload;
// Sync to localStorage
if (typeof window !== 'undefined') {
localStorage.setItem(CART_STORAGE_KEYS.COUPON, JSON.stringify(state.coupon));
}
},
updateTip(state, action: PayloadAction<string>) {
state.tip = action.payload;
// Sync to localStorage
if (typeof window !== 'undefined') {
localStorage.setItem(CART_STORAGE_KEYS.TIP, JSON.stringify(state.tip));
}
},
updateTables(state, action: PayloadAction<string[]>) {
state.tables = [...state.tables, ...action.payload];
// Sync to localStorage
if (typeof window !== 'undefined') {
localStorage.setItem(CART_STORAGE_KEYS.TABLES, JSON.stringify(state.tables));
}
},
removeTable(state) {
state.tables = [];
// Sync to localStorage
if (typeof window !== 'undefined') {
localStorage.removeItem(CART_STORAGE_KEYS.TABLES);
}
},
setTmp(state, action: PayloadAction<unknown>) {
state.tmp = action.payload;
},
updateLocation(state, action: PayloadAction<LocationData | null>) {
state.location = action.payload;
// Sync to localStorage
if (typeof window !== 'undefined') {
localStorage.setItem(CART_STORAGE_KEYS.LOCATION, JSON.stringify(state.location));
}
},
clearLocation(state) {
state.location = null;
// Sync to localStorage
if (typeof window !== 'undefined') {
localStorage.removeItem(CART_STORAGE_KEYS.LOCATION);
}
},
updateRoomDetails(state, action: PayloadAction<RoomDetailsType | null>) {
state.roomDetails = action.payload;
// Sync to localStorage
if (typeof window !== 'undefined') {
localStorage.setItem(CART_STORAGE_KEYS.ROOM_DETAILS, JSON.stringify(state.roomDetails));
}
},
updateOfficeDetails(state, action: PayloadAction<OfficeDetailsType | null>) {
state.officeDetails = action.payload;
// Sync to localStorage
if (typeof window !== 'undefined') {
localStorage.setItem(CART_STORAGE_KEYS.OFFICE_DETAILS, JSON.stringify(state.officeDetails));
}
},
updateGiftDetails(state, action: PayloadAction<GiftDetailsType | null>) {
state.giftDetails = action.payload;
// Sync to localStorage
if (typeof window !== 'undefined') {
localStorage.setItem(CART_STORAGE_KEYS.GIFT_DETAILS, JSON.stringify(state.giftDetails));
}
},
updateEstimateTime(state, action: PayloadAction<{ date: Date; time: string }>) {
state.estimateTime = action.payload.date;
state.estimateTimeDate = action.payload.date;
state.estimateTimeTime = action.payload.time;
// Sync to localStorage
if (typeof window !== 'undefined') {
localStorage.setItem(CART_STORAGE_KEYS.ESTIMATE_TIME, JSON.stringify(state.estimateTime.toISOString()));
localStorage.setItem(CART_STORAGE_KEYS.ESTIMATE_TIME_DATE, JSON.stringify(state.estimateTimeDate.toISOString()));
localStorage.setItem(CART_STORAGE_KEYS.ESTIMATE_TIME_TIME, JSON.stringify(state.estimateTimeTime));
}
},
updateCollectionMethod(state, action: PayloadAction<string>) {
state.collectionMethod = action.payload;
// Sync to localStorage
if (typeof window !== 'undefined') {
localStorage.setItem(CART_STORAGE_KEYS.COLLECTION_METHOD, JSON.stringify(state.collectionMethod));
}
},
},
});
export const {
addItem,
updateQuantity,
removeItem,
clearCart,
updateSpecialRequest,
clearSpecialRequest,
updateCoupon,
updateTip,
updateTables,
removeTable,
setTmp,
updateLocation,
clearLocation,
updateRoomDetails,
updateOfficeDetails,
updateGiftDetails,
updateEstimateTime,
updateCollectionMethod,
reset,
} = orderSlice.actions;
// Selectors
export const selectCart = (state: RootState) => state.order;
export const selectCartItems = (state: RootState) => state.order.items;
export const selectCartTotal = (state: RootState) =>
state.order.items.reduce((total, item) => total + item.price * item.quantity, 0);
export const selectCartItemsQuantity = (id: number | string) => (state: RootState) => {
const item = state.order.items.find((i) => i.id === id);
return item ? item.quantity : 0;
};
export default orderSlice.reducer;

View File

@@ -0,0 +1,62 @@
import { createSlice } from "@reduxjs/toolkit";
import { RootState } from "redux/store";
interface ThemeState {
themeName: string;
}
// Theme persistence utilities
const THEME_STORAGE_KEY = "theme";
const getInitialTheme = (): string => {
if (typeof window !== "undefined") {
try {
const savedTheme = localStorage.getItem(THEME_STORAGE_KEY);
// Validate theme value
if (savedTheme === "light" || savedTheme === "dark") {
return savedTheme;
}
} catch (error) {
console.warn("Failed to read theme from localStorage:", error);
}
}
return "light";
};
const saveThemeToStorage = (theme: string): void => {
if (typeof window !== "undefined") {
try {
localStorage.setItem(THEME_STORAGE_KEY, theme);
} catch (error) {
console.warn("Failed to save theme to localStorage:", error);
}
}
};
const initialState: ThemeState = {
themeName: getInitialTheme(),
};
const themeSlice = createSlice({
name: "theme",
initialState,
reducers: {
toggleTheme: (state: ThemeState) => {
state.themeName = state.themeName === "light" ? "dark" : "light";
saveThemeToStorage(state.themeName);
},
setTheme: (state: ThemeState, action: { payload: string }) => {
// Validate theme value
if (action.payload === "light" || action.payload === "dark") {
state.themeName = action.payload;
saveThemeToStorage(state.themeName);
}
},
},
});
export const { toggleTheme, setTheme } = themeSlice.actions;
export const selectTheme = (state: RootState) => state.theme;
export default themeSlice.reducer;