Files
dmail-admin/src/pages/setting/personality/store/Store.ts
T
2025-08-06 16:10:30 +03:30

818 lines
21 KiB
TypeScript

import { create } from "zustand";
import { v4 as uuidv4 } from "uuid";
import {
PersonalityStore,
PersonalityDataType,
SectionItemType,
TextType,
ButtonType,
ImageType,
SocialType,
HorizontalAlignment,
VerticalAlignment,
SelectedElement,
} from "../types/Types";
// Helper function to get sections sorted by sortNumber
export const getSortedSectionKeys = (data: PersonalityDataType): string[] => {
return Object.keys(data).sort((a, b) => {
const sortA = data[a].sortNumber || 0;
const sortB = data[b].sortNumber || 0;
return sortA - sortB;
});
};
export const usePersonalityStore = create<PersonalityStore>((set, get) => ({
data: {
header: { name: "هدر", columnsCount: 1, items: [], sortNumber: 1 },
content: {
name: "محتوا",
columnsCount: 1,
items: [],
isContent: true,
sortNumber: 2,
},
footer: { name: "فوتر", columnsCount: 1, items: [], sortNumber: 3 },
},
activeSection: "",
activeItemIndex: 0,
selectedElement: null,
isEditMode: false,
setData: (data: PersonalityDataType) =>
set(() => {
// Normalize data to ensure all sections have names
const normalizedData: PersonalityDataType = {};
Object.entries(data).forEach(([key, section]) => {
normalizedData[key] = {
...section,
name:
section.name ||
(key === "header"
? "هدر"
: key === "content"
? "محتوا"
: key === "footer"
? "فوتر"
: `سکشن ${key}`),
};
});
return { data: normalizedData };
}),
setActiveSection: (section: string) =>
set((state) => {
const sectionData = state.data[section];
if (!sectionData) {
return state;
}
// اگر بخش آیتم ندارد و تعداد ستون‌ها بیشتر از صفر است، یک آیتم پیش‌فرض بساز
if (sectionData.items.length === 0 && sectionData.columnsCount > 0) {
const defaultItem = {
id: uuidv4(),
backgroundColor: "#ffffff",
texts: [],
buttons: [],
images: [],
socials: [],
alignment: HorizontalAlignment.CENTER,
verticalAlignment: VerticalAlignment.MIDDLE,
};
return {
activeSection: section,
activeItemIndex: 0,
selectedElement: null,
isEditMode: false,
data: {
...state.data,
[section]: {
...sectionData,
items: [defaultItem],
},
},
};
}
return {
activeSection: section,
activeItemIndex: 0,
selectedElement: null,
isEditMode: false,
};
}),
setActiveItemIndex: (index: number) =>
set(() => {
return {
activeItemIndex: index,
selectedElement: null,
isEditMode: false,
};
}),
setSelectedElement: (element: SelectedElement) =>
set({ selectedElement: element, isEditMode: !!element }),
setEditMode: (isEdit: boolean) => set({ isEditMode: isEdit }),
addSection: (afterSectionKey?: string) =>
set((state) => {
// Generate automatic section name
const existingSectionNumbers = Object.values(state.data)
.map((section) => {
if (!section.name) return 0;
const match = section.name.match(/سکشن (\d+)/);
return match ? parseInt(match[1]) : 0;
})
.filter((num) => num > 0);
const nextNumber =
existingSectionNumbers.length > 0
? Math.max(...existingSectionNumbers) + 1
: 1;
const sectionName = `سکشن ${nextNumber}`;
const sectionKey = `section_${nextNumber}`;
if (state.data[sectionKey]) {
return state; // Section already exists
}
// Calculate the sortNumber for the new section
let newSortNumber = 1;
if (afterSectionKey) {
const afterSection = state.data[afterSectionKey];
if (afterSection) {
// Insert after the specified section, so sortNumber should be higher
newSortNumber = afterSection.sortNumber + 1;
// Update sortNumbers of sections that come after
Object.keys(state.data).forEach((key) => {
if (state.data[key].sortNumber > afterSection.sortNumber) {
state.data[key].sortNumber += 1;
}
});
}
} else {
// Add at the end - find the highest sortNumber and add 1
const maxSortNumber = Math.max(
...Object.values(state.data).map((section) => section.sortNumber || 0)
);
newSortNumber = maxSortNumber + 1;
}
// Create new section data
const newSection = {
name: sectionName,
columnsCount: 1,
items: [],
sortNumber: newSortNumber,
};
// If afterSectionKey is provided, insert after that section
if (afterSectionKey) {
const keys = Object.keys(state.data);
const afterIndex = keys.indexOf(afterSectionKey);
// TODO all index expect that increase +
if (afterIndex !== -1) {
const newData: typeof state.data = {};
// Add sections before the target
for (let i = 0; i <= afterIndex; i++) {
newData[keys[i]] = state.data[keys[i]];
}
// Add new section
newData[sectionKey] = newSection;
// Add remaining sections
for (let i = afterIndex + 1; i < keys.length; i++) {
newData[keys[i]] = state.data[keys[i]];
}
return { data: newData };
}
}
// Default: add at the end
return {
data: {
...state.data,
[sectionKey]: newSection,
},
};
}),
removeSection: (sectionKey: string) =>
set((state) => {
// Cannot remove content section
if (state.data[sectionKey]?.isContent) {
return state;
}
const newData = { ...state.data };
delete newData[sectionKey];
return {
data: newData,
activeSection:
state.activeSection === sectionKey ? "" : state.activeSection,
selectedElement: null,
isEditMode: false,
};
}),
setColumnsCount: (sectionKey: string, columnsCount: number) =>
set((state) => {
const currentItems = state.data[sectionKey].items;
const currentActiveItem = currentItems[state.activeItemIndex];
const newItems = Array.from({ length: columnsCount }, (_, index) => {
// اگر این آیتم در حال حاضر وجود دارد، آن را حفظ کن
if (currentItems[index]) {
return currentItems[index];
}
// اگر این اولین آیتم است و آیتم فعلی وجود دارد، از آن استفاده کن
if (index === 0 && currentActiveItem) {
return {
...currentActiveItem,
id: currentActiveItem.id || uuidv4(),
};
}
// در غیر این صورت، یک آیتم جدید بساز
return {
id: uuidv4(),
backgroundColor: "#ffffff",
texts: [],
buttons: [],
images: [],
socials: [],
alignment: HorizontalAlignment.CENTER,
verticalAlignment: VerticalAlignment.MIDDLE,
};
});
return {
data: {
...state.data,
[sectionKey]: {
...state.data[sectionKey],
columnsCount,
items: newItems,
},
},
activeItemIndex: 0,
};
}),
setSectionPadding: (sectionKey: string, padding: string) =>
set((state) => ({
data: {
...state.data,
[sectionKey]: {
...state.data[sectionKey],
padding,
},
},
})),
setSectionHeight: (sectionKey: string, height: string) =>
set((state) => ({
data: {
...state.data,
[sectionKey]: {
...state.data[sectionKey],
height,
},
},
})),
setSectionBorder: (
sectionKey: string,
border: {
width?: string;
color?: string;
style?: "solid" | "dashed" | "dotted" | "none";
sides?: ("top" | "bottom" | "left" | "right")[];
}
) =>
set((state) => ({
data: {
...state.data,
[sectionKey]: {
...state.data[sectionKey],
border,
},
},
})),
setItems: (sectionKey: string, items: SectionItemType[]) =>
set((state) => ({
data: {
...state.data,
[sectionKey]: {
...state.data[sectionKey],
items,
},
},
})),
addItem: (sectionKey: string, item: Omit<SectionItemType, "id">) =>
set((state) => ({
data: {
...state.data,
[sectionKey]: {
...state.data[sectionKey],
items: [...state.data[sectionKey].items, { ...item, id: uuidv4() }],
},
},
})),
updateActiveItem: (updates: Partial<SectionItemType>) =>
set((state) => {
if (!state.activeSection) return state;
const sectionKey = state.activeSection;
const items = state.data[sectionKey].items;
const activeIndex = state.activeItemIndex;
// اگر آیتم فعال وجود ندارد، یکی بساز
if (activeIndex >= items.length) {
const newItem = {
id: uuidv4(),
backgroundColor: "#ffffff",
texts: [],
buttons: [],
images: [],
socials: [],
alignment: HorizontalAlignment.CENTER,
verticalAlignment: VerticalAlignment.MIDDLE,
...updates,
};
const newItems = [...items];
newItems[activeIndex] = newItem;
return {
data: {
...state.data,
[sectionKey]: {
...state.data[sectionKey],
items: newItems,
},
},
};
}
const updatedItems = items.map((item, index) =>
index === activeIndex ? { ...item, ...updates } : item
);
return {
data: {
...state.data,
[sectionKey]: {
...state.data[sectionKey],
items: updatedItems,
},
},
};
}),
updateItem: (
sectionKey: string,
id: string,
updates: Partial<SectionItemType>
) =>
set((state) => ({
data: {
...state.data,
[sectionKey]: {
...state.data[sectionKey],
items: state.data[sectionKey].items.map((item) =>
item.id === id ? { ...item, ...updates } : item
),
},
},
})),
removeItem: (sectionKey: string, id: string) =>
set((state) => ({
data: {
...state.data,
[sectionKey]: {
...state.data[sectionKey],
items: state.data[sectionKey].items.filter((item) => item.id !== id),
},
},
})),
addText: (sectionKey: string, itemId: string, text: Omit<TextType, "id">) =>
set((state) => ({
data: {
...state.data,
[sectionKey]: {
...state.data[sectionKey],
items: state.data[sectionKey].items.map((item) =>
item.id === itemId
? {
...item,
texts: [...(item.texts || []), { ...text, id: uuidv4() }],
}
: item
),
},
},
})),
addTextToActiveItem: (text: Omit<TextType, "id">) =>
set((state) => {
if (!state.activeSection) return state;
const sectionKey = state.activeSection as string;
const items = state.data[sectionKey].items;
const activeIndex = state.activeItemIndex;
if (activeIndex >= items.length) return state;
const updatedItems = items.map((item, index) =>
index === activeIndex
? {
...item,
texts: [...(item.texts || []), { ...text, id: uuidv4() }],
}
: item
);
return {
data: {
...state.data,
[sectionKey]: {
...state.data[sectionKey],
items: updatedItems,
},
},
};
}),
addButton: (
sectionKey: string,
itemId: string,
button: Omit<ButtonType, "id">
) =>
set((state) => ({
data: {
...state.data,
[sectionKey]: {
...state.data[sectionKey],
items: state.data[sectionKey].items.map((item) =>
item.id === itemId
? {
...item,
buttons: [
...(item.buttons || []),
{ ...button, id: uuidv4() },
],
}
: item
),
},
},
})),
addButtonToActiveItem: (button: Omit<ButtonType, "id">) =>
set((state) => {
if (!state.activeSection) return state;
const sectionKey = state.activeSection as string;
const items = state.data[sectionKey].items;
const activeIndex = state.activeItemIndex;
if (activeIndex >= items.length) return state;
const updatedItems = items.map((item, index) =>
index === activeIndex
? {
...item,
buttons: [...(item.buttons || []), { ...button, id: uuidv4() }],
}
: item
);
return {
data: {
...state.data,
[sectionKey]: {
...state.data[sectionKey],
items: updatedItems,
},
},
};
}),
updateText: (
sectionKey: string,
itemId: string,
textId: string,
updates: Partial<TextType>
) =>
set((state) => ({
data: {
...state.data,
[sectionKey]: {
...state.data[sectionKey],
items: state.data[sectionKey].items.map((item) =>
item.id === itemId
? {
...item,
texts: (item.texts || []).map((text) =>
text.id === textId ? { ...text, ...updates } : text
),
}
: item
),
},
},
})),
updateButton: (
sectionKey: string,
itemId: string,
buttonId: string,
updates: Partial<ButtonType>
) =>
set((state) => ({
data: {
...state.data,
[sectionKey]: {
...state.data[sectionKey],
items: state.data[sectionKey].items.map((item) =>
item.id === itemId
? {
...item,
buttons: (item.buttons || []).map((button) =>
button.id === buttonId ? { ...button, ...updates } : button
),
}
: item
),
},
},
})),
addImage: (
sectionKey: string,
itemId: string,
image: Omit<ImageType, "id">
) =>
set((state) => ({
data: {
...state.data,
[sectionKey]: {
...state.data[sectionKey],
items: state.data[sectionKey].items.map((item) =>
item.id === itemId
? {
...item,
images: [...(item.images || []), { ...image, id: uuidv4() }],
}
: item
),
},
},
})),
addImageToActiveItem: (image: Omit<ImageType, "id">) =>
set((state) => {
if (!state.activeSection) return state;
const sectionKey = state.activeSection as string;
const items = state.data[sectionKey].items;
const activeIndex = state.activeItemIndex;
if (activeIndex >= items.length) return state;
const updatedItems = items.map((item, index) =>
index === activeIndex
? {
...item,
images: [...(item.images || []), { ...image, id: uuidv4() }],
}
: item
);
return {
data: {
...state.data,
[sectionKey]: {
...state.data[sectionKey],
items: updatedItems,
},
},
};
}),
updateImage: (
sectionKey: string,
itemId: string,
imageId: string,
updates: Partial<ImageType>
) =>
set((state) => ({
data: {
...state.data,
[sectionKey]: {
...state.data[sectionKey],
items: state.data[sectionKey].items.map((item) =>
item.id === itemId
? {
...item,
images: (item.images || []).map((image) =>
image.id === imageId ? { ...image, ...updates } : image
),
}
: item
),
},
},
})),
deleteText: (sectionKey: string, itemId: string, textId: string) =>
set((state) => ({
data: {
...state.data,
[sectionKey]: {
...state.data[sectionKey],
items: state.data[sectionKey].items.map((item) =>
item.id === itemId
? {
...item,
texts: (item.texts || []).filter(
(text) => text.id !== textId
),
}
: item
),
},
},
selectedElement: null,
isEditMode: false,
})),
deleteButton: (sectionKey: string, itemId: string, buttonId: string) =>
set((state) => ({
data: {
...state.data,
[sectionKey]: {
...state.data[sectionKey],
items: state.data[sectionKey].items.map((item) =>
item.id === itemId
? {
...item,
buttons: (item.buttons || []).filter(
(button) => button.id !== buttonId
),
}
: item
),
},
},
selectedElement: null,
isEditMode: false,
})),
deleteImage: (sectionKey: string, itemId: string, imageId: string) =>
set((state) => ({
data: {
...state.data,
[sectionKey]: {
...state.data[sectionKey],
items: state.data[sectionKey].items.map((item) =>
item.id === itemId
? {
...item,
images: (item.images || []).filter(
(image) => image.id !== imageId
),
}
: item
),
},
},
selectedElement: null,
isEditMode: false,
})),
addSocial: (
sectionKey: string,
itemId: string,
social: Omit<SocialType, "id">
) =>
set((state) => ({
data: {
...state.data,
[sectionKey]: {
...state.data[sectionKey],
items: state.data[sectionKey].items.map((item) =>
item.id === itemId
? {
...item,
socials: [
...(item.socials || []),
{ ...social, id: uuidv4() },
],
}
: item
),
},
},
})),
addSocialToActiveItem: (social: Omit<SocialType, "id">) =>
set((state) => {
if (!state.activeSection) return state;
const sectionKey = state.activeSection as string;
const items = state.data[sectionKey].items;
const activeIndex = state.activeItemIndex;
if (activeIndex >= items.length) return state;
const updatedItems = items.map((item, index) =>
index === activeIndex
? {
...item,
socials: [...(item.socials || []), { ...social, id: uuidv4() }],
}
: item
);
return {
data: {
...state.data,
[sectionKey]: {
...state.data[sectionKey],
items: updatedItems,
},
},
};
}),
updateSocial: (
sectionKey: string,
itemId: string,
socialId: string,
updates: Partial<SocialType>
) =>
set((state) => ({
data: {
...state.data,
[sectionKey]: {
...state.data[sectionKey],
items: state.data[sectionKey].items.map((item) =>
item.id === itemId
? {
...item,
socials: (item.socials || []).map((social) =>
social.id === socialId ? { ...social, ...updates } : social
),
}
: item
),
},
},
})),
deleteSocial: (sectionKey: string, itemId: string, socialId: string) =>
set((state) => ({
data: {
...state.data,
[sectionKey]: {
...state.data[sectionKey],
items: state.data[sectionKey].items.map((item) =>
item.id === itemId
? {
...item,
socials: (item.socials || []).filter(
(social) => social.id !== socialId
),
}
: item
),
},
},
selectedElement: null,
isEditMode: false,
})),
getSortedSectionKeys: () => getSortedSectionKeys(get().data),
}));