Remove some comments
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
VITE_TOKEN_NAME = 'dsc_token'
|
VITE_TOKEN_NAME = 'dsc_token'
|
||||||
VITE_REFRESH_TOKEN_NAME = 'dsc_refresh_token'
|
VITE_REFRESH_TOKEN_NAME = 'dsc_refresh_token'
|
||||||
VITE_DANAK_BASE_URL ='https://api.danakcorp.com'
|
VITE_DANAK_BASE_URL ='https://api.danakcorp.com'
|
||||||
# VITE_BASE_URL = 'http://192.168.1.113:4000'
|
# VITE_BASE_URL = 'http://192.168.1.117:4000'
|
||||||
VITE_BASE_URL = 'https://dmail-api.danakcorp.com'
|
VITE_BASE_URL = 'https://dmail-api.danakcorp.com'
|
||||||
VITE_SERVICE_ID = 'e51afdc3-ea0b-49cf-8f49-2a6f131b024e'
|
VITE_SERVICE_ID = 'e51afdc3-ea0b-49cf-8f49-2a6f131b024e'
|
||||||
|
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ const AddressForm: FC<AddressFormProps> = ({ selectedAddress, isEditMode, onCanc
|
|||||||
password: params.password || undefined,
|
password: params.password || undefined,
|
||||||
quota: params.quotaMB ? params.quotaMB * 1024 * 1024 : undefined,
|
quota: params.quotaMB ? params.quotaMB * 1024 * 1024 : undefined,
|
||||||
forwarders: forwarders,
|
forwarders: forwarders,
|
||||||
|
templateId: params.templateId,
|
||||||
}
|
}
|
||||||
updateAddress({ id: selectedAddress.id, params: updateParams }, {
|
updateAddress({ id: selectedAddress.id, params: updateParams }, {
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export type UpdateAddressType = {
|
|||||||
aliases?: string[];
|
aliases?: string[];
|
||||||
title?: string;
|
title?: string;
|
||||||
forwarders?: string[];
|
forwarders?: string[];
|
||||||
|
templateId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface AddressData extends Record<string, unknown> {
|
export interface AddressData extends Record<string, unknown> {
|
||||||
|
|||||||
@@ -2,13 +2,16 @@ import { FC, useEffect } from 'react'
|
|||||||
import HeaderSection from './components/HeaderSection'
|
import HeaderSection from './components/HeaderSection'
|
||||||
import ContentSection from './components/ContentSection'
|
import ContentSection from './components/ContentSection'
|
||||||
import FooterSection from './components/FooterSection'
|
import FooterSection from './components/FooterSection'
|
||||||
|
import GenericSection from './components/GenericSection'
|
||||||
import { useParams } from 'react-router-dom'
|
import { useParams } from 'react-router-dom'
|
||||||
import { usePersonalityStore } from './store/Store'
|
import { usePersonalityStore } from './store/Store'
|
||||||
import { useGetTemplate } from './hooks/usePersonalityData'
|
import { useGetTemplate } from './hooks/usePersonalityData'
|
||||||
|
import { Add } from 'iconsax-react'
|
||||||
|
import { toast } from '@/components/Toast'
|
||||||
|
|
||||||
const Personality: FC = () => {
|
const Personality: FC = () => {
|
||||||
const { id } = useParams()
|
const { id } = useParams()
|
||||||
const { setData } = usePersonalityStore()
|
const { setData, data, addSection } = usePersonalityStore()
|
||||||
const { data: template } = useGetTemplate(id || '')
|
const { data: template } = useGetTemplate(id || '')
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -17,13 +20,71 @@ const Personality: FC = () => {
|
|||||||
}
|
}
|
||||||
}, [id, template, setData])
|
}, [id, template, setData])
|
||||||
|
|
||||||
|
const handleAddSection = (afterSectionKey: string) => {
|
||||||
|
addSection(afterSectionKey)
|
||||||
|
toast('سکشن جدید با موفقیت اضافه شد', 'success')
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderSection = (sectionKey: string) => {
|
||||||
|
if (sectionKey === 'header') {
|
||||||
|
return <HeaderSection key={sectionKey} />
|
||||||
|
} else if (sectionKey === 'content') {
|
||||||
|
return <ContentSection key={sectionKey} />
|
||||||
|
} else if (sectionKey === 'footer') {
|
||||||
|
return <FooterSection key={sectionKey} />
|
||||||
|
}
|
||||||
|
|
||||||
|
// For custom sections, use GenericSection with full functionality
|
||||||
|
return <GenericSection key={sectionKey} sectionKey={sectionKey} />
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderPlusButton = (afterSectionKey: string) => {
|
||||||
|
return (
|
||||||
|
<tr key={`plus-${afterSectionKey}`} style={{ position: 'relative', height: '0px' }}>
|
||||||
|
<td style={{ position: 'relative', height: '0px' }}>
|
||||||
|
<div
|
||||||
|
className="absolute left-1/2 transform -translate-x-1/2 -translate-y-1/2"
|
||||||
|
style={{ top: '0px', zIndex: 9999 }}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
onClick={() => handleAddSection(afterSectionKey)}
|
||||||
|
className="group flex items-center justify-center w-10 h-10 rounded-full border-2 border-dashed border-gray-300 hover:border-blue-500 hover:bg-blue-50 bg-white transition-all duration-200 shadow-sm hover:shadow-md"
|
||||||
|
title="اضافه کردن سکشن جدید"
|
||||||
|
style={{ position: 'relative', zIndex: 9999 }}
|
||||||
|
>
|
||||||
|
<Add
|
||||||
|
size={20}
|
||||||
|
color='gray'
|
||||||
|
className="text-gray-400 group-hover:text-blue-500 transition-colors"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex-1 bg-white rounded-4xl p-8 personality-template'>
|
<div className='flex-1 bg-white rounded-4xl p-8 personality-template'>
|
||||||
<table width="100%" cellPadding="0" cellSpacing="0" border={0} style={{ borderCollapse: 'collapse' }}>
|
<table width="100%" cellPadding="0" cellSpacing="0" border={0} style={{ borderCollapse: 'collapse' }}>
|
||||||
<tbody>
|
<tbody>
|
||||||
<HeaderSection />
|
{Object.keys(data).map((sectionKey, index) => {
|
||||||
<ContentSection />
|
const sections = Object.keys(data);
|
||||||
<FooterSection />
|
const elements = [];
|
||||||
|
|
||||||
|
// Add the section
|
||||||
|
elements.push(renderSection(sectionKey));
|
||||||
|
|
||||||
|
// Add plus button after each section except the last one
|
||||||
|
if (index < sections.length - 1) {
|
||||||
|
elements.push(renderPlusButton(sectionKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
return elements;
|
||||||
|
}).flat()}
|
||||||
|
|
||||||
|
{/* Plus button at the end */}
|
||||||
|
{Object.keys(data).length > 0 && renderPlusButton(Object.keys(data)[Object.keys(data).length - 1])}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import ImageSideBar from './components/ImageSideBar'
|
|||||||
import SocialSidebar from './components/SocialSidebar'
|
import SocialSidebar from './components/SocialSidebar'
|
||||||
import { clx } from '@/helpers/utils'
|
import { clx } from '@/helpers/utils'
|
||||||
import { usePersonalityStore } from './store/Store'
|
import { usePersonalityStore } from './store/Store'
|
||||||
import { ElementType, SectionName } from './types/Types'
|
import { ElementType } from './types/Types'
|
||||||
|
|
||||||
const PersonalitySidebar: FC = () => {
|
const PersonalitySidebar: FC = () => {
|
||||||
|
|
||||||
@@ -94,7 +94,7 @@ const PersonalitySidebar: FC = () => {
|
|||||||
className='cursor-pointer'
|
className='cursor-pointer'
|
||||||
/>
|
/>
|
||||||
{
|
{
|
||||||
activeSection !== SectionName.CONTENT && <>
|
activeSection !== 'content' && <>
|
||||||
<Text
|
<Text
|
||||||
size={22}
|
size={22}
|
||||||
color={'black'}
|
color={'black'}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
import { ButtonType, ButtonSize, HorizontalAlignment, VerticalAlignment, ElementType, PersonalityDataType } from '../types/Types'
|
import { ButtonType, ButtonSize, HorizontalAlignment, VerticalAlignment, ElementType, PersonalityDataType, SectionName } from '../types/Types'
|
||||||
import { usePersonalityStore } from '../store/Store'
|
import { usePersonalityStore } from '../store/Store'
|
||||||
|
|
||||||
interface ButtonRendererProps {
|
interface ButtonRendererProps {
|
||||||
@@ -22,7 +22,7 @@ const ButtonRenderer: FC<ButtonRendererProps> = ({ buttons, itemId, sectionKey }
|
|||||||
type: ElementType.BUTTON,
|
type: ElementType.BUTTON,
|
||||||
elementId: buttonId,
|
elementId: buttonId,
|
||||||
itemId,
|
itemId,
|
||||||
sectionKey,
|
sectionKey: sectionKey as SectionName,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -148,6 +148,14 @@ const ContentSection: FC = () => {
|
|||||||
onClick={(e) => handleItemClick(index, e)}
|
onClick={(e) => handleItemClick(index, e)}
|
||||||
>
|
>
|
||||||
{/* محتوای خالی برای columns خالی */}
|
{/* محتوای خالی برای columns خالی */}
|
||||||
|
<div style={{
|
||||||
|
margin: 0,
|
||||||
|
textAlign: 'right',
|
||||||
|
color: '#888',
|
||||||
|
fontSize: '14px'
|
||||||
|
}}>
|
||||||
|
{/* {'{{content}}'} */}
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
) : (
|
) : (
|
||||||
@@ -164,6 +172,14 @@ const ContentSection: FC = () => {
|
|||||||
padding: '8px'
|
padding: '8px'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<div style={{
|
||||||
|
margin: '0 0 10px 0',
|
||||||
|
textAlign: 'right',
|
||||||
|
color: '#888',
|
||||||
|
fontSize: '14px'
|
||||||
|
}}>
|
||||||
|
{'{{content}}'}
|
||||||
|
</div>
|
||||||
<ButtonRenderer
|
<ButtonRenderer
|
||||||
buttons={item.buttons}
|
buttons={item.buttons}
|
||||||
itemId={item?.id || ''}
|
itemId={item?.id || ''}
|
||||||
@@ -202,6 +218,18 @@ const ContentSection: FC = () => {
|
|||||||
itemId={item?.id || ''}
|
itemId={item?.id || ''}
|
||||||
sectionKey="content"
|
sectionKey="content"
|
||||||
/>
|
/>
|
||||||
|
{(!item?.texts || item.texts.length === 0) &&
|
||||||
|
(!item?.images || item.images.length === 0) &&
|
||||||
|
(!item?.socials || item.socials.length === 0) && (
|
||||||
|
<div style={{
|
||||||
|
margin: 0,
|
||||||
|
textAlign: 'right',
|
||||||
|
color: '#888',
|
||||||
|
fontSize: '14px'
|
||||||
|
}}>
|
||||||
|
{'{{content}}'}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/* ردیف دکمهها */}
|
{/* ردیف دکمهها */}
|
||||||
|
|||||||
@@ -0,0 +1,271 @@
|
|||||||
|
import { FC } from 'react'
|
||||||
|
import { usePersonalityStore } from '../store/Store'
|
||||||
|
import { AddCircle } from 'iconsax-react'
|
||||||
|
import TextRenderer from './TextRenderer'
|
||||||
|
import ButtonRenderer from './ButtonRenderer'
|
||||||
|
import ImageRenderer from './ImageRenderer'
|
||||||
|
import SocialRenderer from './SocialRenderer'
|
||||||
|
import { VerticalAlignment, HorizontalAlignment } from '../types/Types'
|
||||||
|
|
||||||
|
interface GenericSectionProps {
|
||||||
|
sectionKey: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const GenericSection: FC<GenericSectionProps> = ({ sectionKey }) => {
|
||||||
|
|
||||||
|
const { activeSection, activeItemIndex, data, setActiveSection, setActiveItemIndex } = usePersonalityStore()
|
||||||
|
|
||||||
|
// Early return if data is not available
|
||||||
|
if (!data || !data[sectionKey]) {
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<td style={{ height: '123px', textAlign: 'center', verticalAlign: 'middle' }}>
|
||||||
|
<div>در حال بارگذاری...</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const sectionData = data[sectionKey]
|
||||||
|
|
||||||
|
const handleSectionClick = (section: string, e: React.MouseEvent) => {
|
||||||
|
|
||||||
|
// اگر کلیک روی فضای خالی باشد (نه روی content elements)
|
||||||
|
const target = e.target as HTMLElement;
|
||||||
|
const isClickOnContent = target.closest('[data-element-type]') !== null;
|
||||||
|
|
||||||
|
if (!isClickOnContent) {
|
||||||
|
setActiveSection(section)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleItemClick = (index: number, e: React.MouseEvent) => {
|
||||||
|
|
||||||
|
// Check if click is on a content element (text, button, image, social)
|
||||||
|
const target = e.target as HTMLElement;
|
||||||
|
const isClickOnContent = target.closest('[data-element-type]') !== null;
|
||||||
|
|
||||||
|
|
||||||
|
// Only select column if not clicking on content elements
|
||||||
|
if (!isClickOnContent) {
|
||||||
|
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
// Only set active section if we're not already in this section
|
||||||
|
if (activeSection !== sectionKey) {
|
||||||
|
|
||||||
|
setActiveSection(sectionKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always set the active item index
|
||||||
|
setActiveItemIndex(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// تابعهای helper برای تعیین alignment دکمهها
|
||||||
|
const getButtonHorizontalAlign = (button?: { alignment?: HorizontalAlignment }) => {
|
||||||
|
if (!button || !button.alignment) return 'center'
|
||||||
|
switch (button.alignment) {
|
||||||
|
case HorizontalAlignment.LEFT:
|
||||||
|
return 'left'
|
||||||
|
case HorizontalAlignment.RIGHT:
|
||||||
|
return 'right'
|
||||||
|
case HorizontalAlignment.CENTER:
|
||||||
|
default:
|
||||||
|
return 'center'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getButtonVerticalAlign = (button?: { verticalAlignment?: VerticalAlignment }) => {
|
||||||
|
if (!button || !button.verticalAlignment) return 'middle'
|
||||||
|
switch (button.verticalAlignment) {
|
||||||
|
case VerticalAlignment.TOP:
|
||||||
|
return 'top'
|
||||||
|
case VerticalAlignment.BOTTOM:
|
||||||
|
return 'bottom'
|
||||||
|
case VerticalAlignment.MIDDLE:
|
||||||
|
default:
|
||||||
|
return 'middle'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr onClick={(e) => handleSectionClick(sectionKey, e)}>
|
||||||
|
{
|
||||||
|
sectionData.columnsCount > 0 ?
|
||||||
|
<td style={{
|
||||||
|
padding: '10px',
|
||||||
|
border: activeSection === sectionKey ? '1px dashed #0038FF' : '1px dashed #E5E7EB',
|
||||||
|
borderRadius: '24px',
|
||||||
|
cursor: 'pointer'
|
||||||
|
}}>
|
||||||
|
<table width="100%" cellPadding="0" cellSpacing="0" border={0} style={{ tableLayout: 'fixed' }}>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
{
|
||||||
|
Array.from({ length: sectionData.columnsCount }, (_, index) => {
|
||||||
|
const item = sectionData.items[index];
|
||||||
|
const isActive = activeSection === sectionKey && activeItemIndex === index;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<td
|
||||||
|
key={index}
|
||||||
|
onClick={(e) => handleItemClick(index, e)}
|
||||||
|
style={{
|
||||||
|
width: `${100 / sectionData.columnsCount}%`,
|
||||||
|
height: '123px',
|
||||||
|
border: isActive ? '1px dashed #0038FF' : '1px dashed #EAECF4',
|
||||||
|
backgroundColor: item?.backgroundColor || '#ffffff',
|
||||||
|
backgroundImage: item?.backgroundImage ? `url(${item.backgroundImage})` : 'none',
|
||||||
|
backgroundSize: item?.style?.backgroundSize || 'cover',
|
||||||
|
backgroundRepeat: item?.style?.backgroundRepeat || 'no-repeat',
|
||||||
|
backgroundPosition: item?.style?.backgroundPosition || 'center',
|
||||||
|
cursor: 'pointer',
|
||||||
|
// padding: '8px',
|
||||||
|
verticalAlign: 'top',
|
||||||
|
borderRadius: '8px',
|
||||||
|
position: 'relative'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<table width="100%" cellPadding="0" cellSpacing="0" border={0} style={{
|
||||||
|
borderCollapse: 'collapse',
|
||||||
|
height: '123px'
|
||||||
|
}}>
|
||||||
|
<tbody>
|
||||||
|
{/* اگر column خالی است */}
|
||||||
|
{(!item?.texts || item.texts.length === 0) &&
|
||||||
|
(!item?.buttons || item.buttons.length === 0) &&
|
||||||
|
(!item?.images || item.images.length === 0) &&
|
||||||
|
(!item?.socials || item.socials.length === 0) ? (
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
width="100%"
|
||||||
|
height="123"
|
||||||
|
style={{
|
||||||
|
padding: '8px',
|
||||||
|
cursor: 'pointer'
|
||||||
|
}}
|
||||||
|
onClick={(e) => handleItemClick(index, e)}
|
||||||
|
>
|
||||||
|
{/* محتوای خالی برای columns خالی */}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{/* اگر فقط دکمه داریم و متن نداریم */}
|
||||||
|
{(!item?.texts || item.texts.length === 0) && item?.buttons && item.buttons.length > 0 ? (
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
width="100%"
|
||||||
|
height="123"
|
||||||
|
align={getButtonHorizontalAlign(item.buttons[0])}
|
||||||
|
valign={getButtonVerticalAlign(item.buttons[0])}
|
||||||
|
style={{
|
||||||
|
padding: '8px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ButtonRenderer
|
||||||
|
buttons={item.buttons}
|
||||||
|
itemId={item?.id || ''}
|
||||||
|
sectionKey={sectionKey}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{/* ردیف اصلی برای متن */}
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
width="100%"
|
||||||
|
height={item?.buttons && item.buttons.length > 0 ? "60" : "87"}
|
||||||
|
align={item?.texts?.[0]?.alignment || 'center'}
|
||||||
|
valign={item?.texts?.[0]?.verticalAlignment === VerticalAlignment.TOP ? 'top' :
|
||||||
|
item?.texts?.[0]?.verticalAlignment === VerticalAlignment.BOTTOM ? 'bottom' : 'middle'}
|
||||||
|
style={{
|
||||||
|
padding: '8px',
|
||||||
|
fontSize: '14px',
|
||||||
|
lineHeight: '1.4'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextRenderer
|
||||||
|
texts={item?.texts || []}
|
||||||
|
itemId={item?.id || ''}
|
||||||
|
sectionKey={sectionKey}
|
||||||
|
/>
|
||||||
|
<ImageRenderer
|
||||||
|
images={item?.images || []}
|
||||||
|
itemId={item?.id || ''}
|
||||||
|
sectionKey={sectionKey}
|
||||||
|
/>
|
||||||
|
<SocialRenderer
|
||||||
|
socials={item?.socials || []}
|
||||||
|
itemId={item?.id || ''}
|
||||||
|
sectionKey={sectionKey}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{/* ردیف دکمهها */}
|
||||||
|
{item?.buttons && item.buttons.length > 0 && (
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
width="100%"
|
||||||
|
height="27"
|
||||||
|
align={getButtonHorizontalAlign(item.buttons[0])}
|
||||||
|
valign={getButtonVerticalAlign(item.buttons[0])}
|
||||||
|
style={{
|
||||||
|
padding: '0 8px 8px 8px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ButtonRenderer
|
||||||
|
buttons={item.buttons}
|
||||||
|
itemId={item?.id || ''}
|
||||||
|
sectionKey={sectionKey}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
{index < sectionData.columnsCount - 1 && (
|
||||||
|
<td style={{ width: '16px' }}></td>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
:
|
||||||
|
<td
|
||||||
|
onClick={(e) => handleSectionClick(sectionKey, e)}
|
||||||
|
style={{
|
||||||
|
height: '123px',
|
||||||
|
border: activeSection === sectionKey ? '1px dashed #0038FF' : '1px dashed #E5E7EB',
|
||||||
|
borderRadius: '24px',
|
||||||
|
textAlign: 'center',
|
||||||
|
verticalAlign: 'middle',
|
||||||
|
padding: '10px',
|
||||||
|
cursor: 'pointer'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ color: '#888', fontSize: '14px', marginBottom: '8px' }}>
|
||||||
|
{data[sectionKey]?.name || sectionKey}
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
|
||||||
|
<AddCircle size={24} color='#888' />
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
}
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GenericSection
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
import { ImageType, ImageSize, HorizontalAlignment, VerticalAlignment, ElementType, PersonalityDataType } from '../types/Types'
|
import { ImageType, ImageSize, HorizontalAlignment, VerticalAlignment, ElementType, PersonalityDataType, SectionName } from '../types/Types'
|
||||||
import { usePersonalityStore } from '../store/Store'
|
import { usePersonalityStore } from '../store/Store'
|
||||||
|
|
||||||
interface ImageRendererProps {
|
interface ImageRendererProps {
|
||||||
@@ -21,7 +21,7 @@ const ImageRenderer: FC<ImageRendererProps> = ({ images, itemId, sectionKey }) =
|
|||||||
type: ElementType.IMAGE,
|
type: ElementType.IMAGE,
|
||||||
elementId: imageId,
|
elementId: imageId,
|
||||||
itemId,
|
itemId,
|
||||||
sectionKey,
|
sectionKey: sectionKey as SectionName,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,16 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import UploadBoxDraggble from '@/components/UploadBoxDraggble';
|
import UploadBoxDraggble from '@/components/UploadBoxDraggble';
|
||||||
import ColorPicker from '@/components/ColorPicker';
|
import ColorPicker from '@/components/ColorPicker';
|
||||||
import { usePersonalityStore } from '../store/Store';
|
import { usePersonalityStore } from '../store/Store';
|
||||||
import { SectionItemType, BackgroundSize, BackgroundPosition, SectionName } from '../types/Types';
|
import { SectionItemType, BackgroundSize, BackgroundPosition } from '../types/Types';
|
||||||
import Select from '@/components/Select';
|
import Select from '@/components/Select';
|
||||||
import { useSingleUpload } from '../hooks/usePersonalityData';
|
import { useSingleUpload } from '../hooks/usePersonalityData';
|
||||||
import { toast } from '@/components/Toast';
|
import { toast } from '@/components/Toast';
|
||||||
|
import Button from '@/components/Button';
|
||||||
|
import { Trash } from 'iconsax-react';
|
||||||
|
|
||||||
const SettingSideBar: FC = () => {
|
const SettingSideBar: FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { activeSection, setColumnsCount, updateActiveItem, data, activeItemIndex } = usePersonalityStore()
|
const { activeSection, setColumnsCount, updateActiveItem, data, activeItemIndex, removeSection } = usePersonalityStore()
|
||||||
const [color, setColor] = useState("#aabbcc");
|
const [color, setColor] = useState("#aabbcc");
|
||||||
const [backgroundSize, setBackgroundSize] = useState<BackgroundSize>(BackgroundSize.COVER)
|
const [backgroundSize, setBackgroundSize] = useState<BackgroundSize>(BackgroundSize.COVER)
|
||||||
const [backgroundPosition, setBackgroundPosition] = useState<BackgroundPosition>(BackgroundPosition.CENTER)
|
const [backgroundPosition, setBackgroundPosition] = useState<BackgroundPosition>(BackgroundPosition.CENTER)
|
||||||
@@ -54,11 +56,22 @@ const SettingSideBar: FC = () => {
|
|||||||
|
|
||||||
const handleColumnClick = (columns: number) => {
|
const handleColumnClick = (columns: number) => {
|
||||||
if (activeSection) {
|
if (activeSection) {
|
||||||
if (activeSection === SectionName.CONTENT && columns > 1) {
|
if (data[activeSection]?.isContent && columns > 1) {
|
||||||
toast('این بخش فقط می تواند یک ستون داشته باشد', 'error')
|
toast('این بخش فقط می تواند یک ستون داشته باشد', 'error')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setColumnsCount(activeSection as SectionName, columns);
|
setColumnsCount(activeSection, columns);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const handleRemoveSection = () => {
|
||||||
|
if (activeSection && !data[activeSection]?.isContent) {
|
||||||
|
removeSection(activeSection)
|
||||||
|
toast('سکشن با موفقیت حذف شد', 'success')
|
||||||
|
} else {
|
||||||
|
toast('نمیتوانید سکشن محتوا را حذف کنید', 'error')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,6 +180,20 @@ const SettingSideBar: FC = () => {
|
|||||||
<div>
|
<div>
|
||||||
<div className='text-lg'>{t('setting.setting')}</div>
|
<div className='text-lg'>{t('setting.setting')}</div>
|
||||||
|
|
||||||
|
{/* Section Delete */}
|
||||||
|
{activeSection && !data[activeSection]?.isContent && (
|
||||||
|
<div className='mt-6 border-b border-gray-200 pb-6'>
|
||||||
|
<Button
|
||||||
|
onClick={handleRemoveSection}
|
||||||
|
variant='secondary'
|
||||||
|
className='w-full flex items-center justify-center gap-2 !text-red-600 !border-red-600 hover:!bg-red-50'
|
||||||
|
>
|
||||||
|
<Trash size={16} />
|
||||||
|
حذف سکشن فعلی
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className='flex h-[51px] gap-5 mt-6'>
|
<div className='flex h-[51px] gap-5 mt-6'>
|
||||||
<div
|
<div
|
||||||
className={`w-[102px] h-full bg-[#EAECF4] transition-colors ${isUploading ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer hover:bg-[#d1d5db]'}`}
|
className={`w-[102px] h-full bg-[#EAECF4] transition-colors ${isUploading ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer hover:bg-[#d1d5db]'}`}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { FC } from 'react'
|
import React, { FC } from 'react'
|
||||||
import { SocialType, SocialNetworkType, SocialIconSize, SocialIconStyle, ElementType, PersonalityDataType, HorizontalAlignment, VerticalAlignment } from '../types/Types'
|
import { SocialType, SocialNetworkType, SocialIconSize, SocialIconStyle, ElementType, PersonalityDataType, HorizontalAlignment, VerticalAlignment, SectionName } from '../types/Types'
|
||||||
import { usePersonalityStore } from '../store/Store'
|
import { usePersonalityStore } from '../store/Store'
|
||||||
import { getSocialIconPath, hasPngIcon, getSocialColor as getSocialColorFromUtils, getSocialSvgIcon } from '../utils/socialIcons'
|
import { getSocialIconPath, hasPngIcon, getSocialColor as getSocialColorFromUtils, getSocialSvgIcon } from '../utils/socialIcons'
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ const SocialRenderer: FC<SocialRendererProps> = ({ socials, itemId, sectionKey }
|
|||||||
type: ElementType.SOCIAL,
|
type: ElementType.SOCIAL,
|
||||||
elementId: socialId,
|
elementId: socialId,
|
||||||
itemId,
|
itemId,
|
||||||
sectionKey,
|
sectionKey: sectionKey as SectionName,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
import { TextType, ElementType, PersonalityDataType } from '../types/Types'
|
import { TextType, ElementType, PersonalityDataType, SectionName } from '../types/Types'
|
||||||
import { usePersonalityStore } from '../store/Store'
|
import { usePersonalityStore } from '../store/Store'
|
||||||
|
|
||||||
interface TextRendererProps {
|
interface TextRendererProps {
|
||||||
@@ -20,7 +20,7 @@ const TextRenderer: FC<TextRendererProps> = ({ texts, itemId, sectionKey }) => {
|
|||||||
type: ElementType.TEXT,
|
type: ElementType.TEXT,
|
||||||
elementId: textId,
|
elementId: textId,
|
||||||
itemId,
|
itemId,
|
||||||
sectionKey,
|
sectionKey: sectionKey as SectionName,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
ButtonType,
|
ButtonType,
|
||||||
ImageType,
|
ImageType,
|
||||||
SocialType,
|
SocialType,
|
||||||
SectionName,
|
|
||||||
HorizontalAlignment,
|
HorizontalAlignment,
|
||||||
VerticalAlignment,
|
VerticalAlignment,
|
||||||
SelectedElement,
|
SelectedElement,
|
||||||
@@ -16,9 +15,9 @@ import {
|
|||||||
|
|
||||||
export const usePersonalityStore = create<PersonalityStore>((set) => ({
|
export const usePersonalityStore = create<PersonalityStore>((set) => ({
|
||||||
data: {
|
data: {
|
||||||
header: { columnsCount: 1, items: [] },
|
header: { name: "هدر", columnsCount: 1, items: [] },
|
||||||
content: { columnsCount: 1, items: [] },
|
content: { name: "محتوا", columnsCount: 1, items: [], isContent: true },
|
||||||
footer: { columnsCount: 1, items: [] },
|
footer: { name: "فوتر", columnsCount: 1, items: [] },
|
||||||
},
|
},
|
||||||
|
|
||||||
activeSection: "",
|
activeSection: "",
|
||||||
@@ -27,13 +26,35 @@ export const usePersonalityStore = create<PersonalityStore>((set) => ({
|
|||||||
isEditMode: false,
|
isEditMode: false,
|
||||||
|
|
||||||
setData: (data: PersonalityDataType) =>
|
setData: (data: PersonalityDataType) =>
|
||||||
set(() => ({
|
set(() => {
|
||||||
data,
|
// Normalize data to ensure all sections have names
|
||||||
})),
|
const normalizedData: PersonalityDataType = {};
|
||||||
|
|
||||||
setActiveSection: (section: SectionName) =>
|
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) => {
|
set((state) => {
|
||||||
const sectionData = state.data[section as keyof PersonalityDataType];
|
const sectionData = state.data[section];
|
||||||
|
|
||||||
|
if (!sectionData) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
// اگر بخش آیتم ندارد و تعداد ستونها بیشتر از صفر است، یک آیتم پیشفرض بساز
|
// اگر بخش آیتم ندارد و تعداد ستونها بیشتر از صفر است، یک آیتم پیشفرض بساز
|
||||||
if (sectionData.items.length === 0 && sectionData.columnsCount > 0) {
|
if (sectionData.items.length === 0 && sectionData.columnsCount > 0) {
|
||||||
@@ -85,10 +106,90 @@ export const usePersonalityStore = create<PersonalityStore>((set) => ({
|
|||||||
|
|
||||||
setEditMode: (isEdit: boolean) => set({ isEditMode: isEdit }),
|
setEditMode: (isEdit: boolean) => set({ isEditMode: isEdit }),
|
||||||
|
|
||||||
setColumnsCount: (
|
addSection: (afterSectionKey?: string) =>
|
||||||
sectionKey: keyof PersonalityDataType,
|
set((state) => {
|
||||||
columnsCount: number
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new section data
|
||||||
|
const newSection = {
|
||||||
|
name: sectionName,
|
||||||
|
columnsCount: 1,
|
||||||
|
items: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
// If afterSectionKey is provided, insert after that section
|
||||||
|
if (afterSectionKey) {
|
||||||
|
const keys = Object.keys(state.data);
|
||||||
|
const afterIndex = keys.indexOf(afterSectionKey);
|
||||||
|
|
||||||
|
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) => {
|
set((state) => {
|
||||||
const currentItems = state.data[sectionKey].items;
|
const currentItems = state.data[sectionKey].items;
|
||||||
const currentActiveItem = currentItems[state.activeItemIndex];
|
const currentActiveItem = currentItems[state.activeItemIndex];
|
||||||
@@ -124,6 +225,7 @@ export const usePersonalityStore = create<PersonalityStore>((set) => ({
|
|||||||
data: {
|
data: {
|
||||||
...state.data,
|
...state.data,
|
||||||
[sectionKey]: {
|
[sectionKey]: {
|
||||||
|
...state.data[sectionKey],
|
||||||
columnsCount,
|
columnsCount,
|
||||||
items: newItems,
|
items: newItems,
|
||||||
},
|
},
|
||||||
@@ -132,7 +234,7 @@ export const usePersonalityStore = create<PersonalityStore>((set) => ({
|
|||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
|
||||||
setItems: (sectionKey: keyof PersonalityDataType, items: SectionItemType[]) =>
|
setItems: (sectionKey: string, items: SectionItemType[]) =>
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
data: {
|
data: {
|
||||||
...state.data,
|
...state.data,
|
||||||
@@ -143,10 +245,7 @@ export const usePersonalityStore = create<PersonalityStore>((set) => ({
|
|||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
|
|
||||||
addItem: (
|
addItem: (sectionKey: string, item: Omit<SectionItemType, "id">) =>
|
||||||
sectionKey: keyof PersonalityDataType,
|
|
||||||
item: Omit<SectionItemType, "id">
|
|
||||||
) =>
|
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
data: {
|
data: {
|
||||||
...state.data,
|
...state.data,
|
||||||
@@ -161,7 +260,7 @@ export const usePersonalityStore = create<PersonalityStore>((set) => ({
|
|||||||
set((state) => {
|
set((state) => {
|
||||||
if (!state.activeSection) return state;
|
if (!state.activeSection) return state;
|
||||||
|
|
||||||
const sectionKey = state.activeSection as keyof PersonalityDataType;
|
const sectionKey = state.activeSection;
|
||||||
const items = state.data[sectionKey].items;
|
const items = state.data[sectionKey].items;
|
||||||
const activeIndex = state.activeItemIndex;
|
const activeIndex = state.activeItemIndex;
|
||||||
|
|
||||||
@@ -209,7 +308,7 @@ export const usePersonalityStore = create<PersonalityStore>((set) => ({
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
updateItem: (
|
updateItem: (
|
||||||
sectionKey: keyof PersonalityDataType,
|
sectionKey: string,
|
||||||
id: string,
|
id: string,
|
||||||
updates: Partial<SectionItemType>
|
updates: Partial<SectionItemType>
|
||||||
) =>
|
) =>
|
||||||
@@ -225,7 +324,7 @@ export const usePersonalityStore = create<PersonalityStore>((set) => ({
|
|||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
|
|
||||||
removeItem: (sectionKey: keyof PersonalityDataType, id: string) =>
|
removeItem: (sectionKey: string, id: string) =>
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
data: {
|
data: {
|
||||||
...state.data,
|
...state.data,
|
||||||
@@ -236,11 +335,7 @@ export const usePersonalityStore = create<PersonalityStore>((set) => ({
|
|||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
|
|
||||||
addText: (
|
addText: (sectionKey: string, itemId: string, text: Omit<TextType, "id">) =>
|
||||||
sectionKey: keyof PersonalityDataType,
|
|
||||||
itemId: string,
|
|
||||||
text: Omit<TextType, "id">
|
|
||||||
) =>
|
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
data: {
|
data: {
|
||||||
...state.data,
|
...state.data,
|
||||||
@@ -262,7 +357,7 @@ export const usePersonalityStore = create<PersonalityStore>((set) => ({
|
|||||||
set((state) => {
|
set((state) => {
|
||||||
if (!state.activeSection) return state;
|
if (!state.activeSection) return state;
|
||||||
|
|
||||||
const sectionKey = state.activeSection as keyof PersonalityDataType;
|
const sectionKey = state.activeSection as string;
|
||||||
const items = state.data[sectionKey].items;
|
const items = state.data[sectionKey].items;
|
||||||
const activeIndex = state.activeItemIndex;
|
const activeIndex = state.activeItemIndex;
|
||||||
|
|
||||||
@@ -289,7 +384,7 @@ export const usePersonalityStore = create<PersonalityStore>((set) => ({
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
addButton: (
|
addButton: (
|
||||||
sectionKey: keyof PersonalityDataType,
|
sectionKey: string,
|
||||||
itemId: string,
|
itemId: string,
|
||||||
button: Omit<ButtonType, "id">
|
button: Omit<ButtonType, "id">
|
||||||
) =>
|
) =>
|
||||||
@@ -317,7 +412,7 @@ export const usePersonalityStore = create<PersonalityStore>((set) => ({
|
|||||||
set((state) => {
|
set((state) => {
|
||||||
if (!state.activeSection) return state;
|
if (!state.activeSection) return state;
|
||||||
|
|
||||||
const sectionKey = state.activeSection as keyof PersonalityDataType;
|
const sectionKey = state.activeSection as string;
|
||||||
const items = state.data[sectionKey].items;
|
const items = state.data[sectionKey].items;
|
||||||
const activeIndex = state.activeItemIndex;
|
const activeIndex = state.activeItemIndex;
|
||||||
|
|
||||||
@@ -344,7 +439,7 @@ export const usePersonalityStore = create<PersonalityStore>((set) => ({
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
updateText: (
|
updateText: (
|
||||||
sectionKey: keyof PersonalityDataType,
|
sectionKey: string,
|
||||||
itemId: string,
|
itemId: string,
|
||||||
textId: string,
|
textId: string,
|
||||||
updates: Partial<TextType>
|
updates: Partial<TextType>
|
||||||
@@ -369,7 +464,7 @@ export const usePersonalityStore = create<PersonalityStore>((set) => ({
|
|||||||
})),
|
})),
|
||||||
|
|
||||||
updateButton: (
|
updateButton: (
|
||||||
sectionKey: keyof PersonalityDataType,
|
sectionKey: string,
|
||||||
itemId: string,
|
itemId: string,
|
||||||
buttonId: string,
|
buttonId: string,
|
||||||
updates: Partial<ButtonType>
|
updates: Partial<ButtonType>
|
||||||
@@ -394,7 +489,7 @@ export const usePersonalityStore = create<PersonalityStore>((set) => ({
|
|||||||
})),
|
})),
|
||||||
|
|
||||||
addImage: (
|
addImage: (
|
||||||
sectionKey: keyof PersonalityDataType,
|
sectionKey: string,
|
||||||
itemId: string,
|
itemId: string,
|
||||||
image: Omit<ImageType, "id">
|
image: Omit<ImageType, "id">
|
||||||
) =>
|
) =>
|
||||||
@@ -419,7 +514,7 @@ export const usePersonalityStore = create<PersonalityStore>((set) => ({
|
|||||||
set((state) => {
|
set((state) => {
|
||||||
if (!state.activeSection) return state;
|
if (!state.activeSection) return state;
|
||||||
|
|
||||||
const sectionKey = state.activeSection as keyof PersonalityDataType;
|
const sectionKey = state.activeSection as string;
|
||||||
const items = state.data[sectionKey].items;
|
const items = state.data[sectionKey].items;
|
||||||
const activeIndex = state.activeItemIndex;
|
const activeIndex = state.activeItemIndex;
|
||||||
|
|
||||||
@@ -446,7 +541,7 @@ export const usePersonalityStore = create<PersonalityStore>((set) => ({
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
updateImage: (
|
updateImage: (
|
||||||
sectionKey: keyof PersonalityDataType,
|
sectionKey: string,
|
||||||
itemId: string,
|
itemId: string,
|
||||||
imageId: string,
|
imageId: string,
|
||||||
updates: Partial<ImageType>
|
updates: Partial<ImageType>
|
||||||
@@ -470,11 +565,7 @@ export const usePersonalityStore = create<PersonalityStore>((set) => ({
|
|||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
|
|
||||||
deleteText: (
|
deleteText: (sectionKey: string, itemId: string, textId: string) =>
|
||||||
sectionKey: keyof PersonalityDataType,
|
|
||||||
itemId: string,
|
|
||||||
textId: string
|
|
||||||
) =>
|
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
data: {
|
data: {
|
||||||
...state.data,
|
...state.data,
|
||||||
@@ -496,11 +587,7 @@ export const usePersonalityStore = create<PersonalityStore>((set) => ({
|
|||||||
isEditMode: false,
|
isEditMode: false,
|
||||||
})),
|
})),
|
||||||
|
|
||||||
deleteButton: (
|
deleteButton: (sectionKey: string, itemId: string, buttonId: string) =>
|
||||||
sectionKey: keyof PersonalityDataType,
|
|
||||||
itemId: string,
|
|
||||||
buttonId: string
|
|
||||||
) =>
|
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
data: {
|
data: {
|
||||||
...state.data,
|
...state.data,
|
||||||
@@ -522,11 +609,7 @@ export const usePersonalityStore = create<PersonalityStore>((set) => ({
|
|||||||
isEditMode: false,
|
isEditMode: false,
|
||||||
})),
|
})),
|
||||||
|
|
||||||
deleteImage: (
|
deleteImage: (sectionKey: string, itemId: string, imageId: string) =>
|
||||||
sectionKey: keyof PersonalityDataType,
|
|
||||||
itemId: string,
|
|
||||||
imageId: string
|
|
||||||
) =>
|
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
data: {
|
data: {
|
||||||
...state.data,
|
...state.data,
|
||||||
@@ -549,7 +632,7 @@ export const usePersonalityStore = create<PersonalityStore>((set) => ({
|
|||||||
})),
|
})),
|
||||||
|
|
||||||
addSocial: (
|
addSocial: (
|
||||||
sectionKey: keyof PersonalityDataType,
|
sectionKey: string,
|
||||||
itemId: string,
|
itemId: string,
|
||||||
social: Omit<SocialType, "id">
|
social: Omit<SocialType, "id">
|
||||||
) =>
|
) =>
|
||||||
@@ -577,7 +660,7 @@ export const usePersonalityStore = create<PersonalityStore>((set) => ({
|
|||||||
set((state) => {
|
set((state) => {
|
||||||
if (!state.activeSection) return state;
|
if (!state.activeSection) return state;
|
||||||
|
|
||||||
const sectionKey = state.activeSection as keyof PersonalityDataType;
|
const sectionKey = state.activeSection as string;
|
||||||
const items = state.data[sectionKey].items;
|
const items = state.data[sectionKey].items;
|
||||||
const activeIndex = state.activeItemIndex;
|
const activeIndex = state.activeItemIndex;
|
||||||
|
|
||||||
@@ -604,7 +687,7 @@ export const usePersonalityStore = create<PersonalityStore>((set) => ({
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
updateSocial: (
|
updateSocial: (
|
||||||
sectionKey: keyof PersonalityDataType,
|
sectionKey: string,
|
||||||
itemId: string,
|
itemId: string,
|
||||||
socialId: string,
|
socialId: string,
|
||||||
updates: Partial<SocialType>
|
updates: Partial<SocialType>
|
||||||
@@ -628,11 +711,7 @@ export const usePersonalityStore = create<PersonalityStore>((set) => ({
|
|||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
|
|
||||||
deleteSocial: (
|
deleteSocial: (sectionKey: string, itemId: string, socialId: string) =>
|
||||||
sectionKey: keyof PersonalityDataType,
|
|
||||||
itemId: string,
|
|
||||||
socialId: string
|
|
||||||
) =>
|
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
data: {
|
data: {
|
||||||
...state.data,
|
...state.data,
|
||||||
|
|||||||
@@ -92,13 +92,11 @@ export type SelectedElement = {
|
|||||||
type: ElementType;
|
type: ElementType;
|
||||||
elementId: string;
|
elementId: string;
|
||||||
itemId: string;
|
itemId: string;
|
||||||
sectionKey: keyof PersonalityDataType;
|
sectionKey: string;
|
||||||
} | null;
|
} | null;
|
||||||
|
|
||||||
export type PersonalityDataType = {
|
export type PersonalityDataType = {
|
||||||
header: SectionType;
|
[key: string]: SectionType & { name: string; isContent?: boolean };
|
||||||
content: SectionType;
|
|
||||||
footer: SectionType;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SectionType = {
|
export type SectionType = {
|
||||||
@@ -175,106 +173,85 @@ export type SocialType = {
|
|||||||
|
|
||||||
export type PersonalityStore = {
|
export type PersonalityStore = {
|
||||||
data: PersonalityDataType;
|
data: PersonalityDataType;
|
||||||
activeSection: SectionName | "";
|
activeSection: string;
|
||||||
activeItemIndex: number;
|
activeItemIndex: number;
|
||||||
selectedElement: SelectedElement; // New field for element selection
|
selectedElement: SelectedElement; // New field for element selection
|
||||||
isEditMode: boolean; // New field for edit mode
|
isEditMode: boolean; // New field for edit mode
|
||||||
|
|
||||||
setData: (data: PersonalityDataType) => void;
|
setData: (data: PersonalityDataType) => void;
|
||||||
setActiveSection: (section: SectionName) => void;
|
setActiveSection: (section: string) => void;
|
||||||
setActiveItemIndex: (index: number) => void;
|
setActiveItemIndex: (index: number) => void;
|
||||||
setSelectedElement: (element: SelectedElement) => void; // New method
|
setSelectedElement: (element: SelectedElement) => void; // New method
|
||||||
setEditMode: (isEdit: boolean) => void; // New method
|
setEditMode: (isEdit: boolean) => void; // New method
|
||||||
|
|
||||||
setItems: (
|
// Section management methods
|
||||||
sectionKey: keyof PersonalityDataType,
|
addSection: (afterSectionKey?: string) => void;
|
||||||
items: SectionItemType[]
|
removeSection: (sectionKey: string) => void;
|
||||||
) => void;
|
|
||||||
addItem: (
|
setItems: (sectionKey: string, items: SectionItemType[]) => void;
|
||||||
sectionKey: keyof PersonalityDataType,
|
addItem: (sectionKey: string, item: Omit<SectionItemType, "id">) => void;
|
||||||
item: Omit<SectionItemType, "id">
|
|
||||||
) => void;
|
|
||||||
updateItem: (
|
updateItem: (
|
||||||
sectionKey: keyof PersonalityDataType,
|
sectionKey: string,
|
||||||
id: string,
|
id: string,
|
||||||
updates: Partial<SectionItemType>
|
updates: Partial<SectionItemType>
|
||||||
) => void;
|
) => void;
|
||||||
updateActiveItem: (updates: Partial<SectionItemType>) => void;
|
updateActiveItem: (updates: Partial<SectionItemType>) => void;
|
||||||
removeItem: (sectionKey: keyof PersonalityDataType, id: string) => void;
|
removeItem: (sectionKey: string, id: string) => void;
|
||||||
setColumnsCount: (
|
setColumnsCount: (sectionKey: string, columnsCount: number) => void;
|
||||||
sectionKey: keyof PersonalityDataType,
|
|
||||||
columnsCount: number
|
|
||||||
) => void;
|
|
||||||
addText: (
|
addText: (
|
||||||
sectionKey: keyof PersonalityDataType,
|
sectionKey: string,
|
||||||
itemId: string,
|
itemId: string,
|
||||||
text: Omit<TextType, "id">
|
text: Omit<TextType, "id">
|
||||||
) => void;
|
) => void;
|
||||||
addTextToActiveItem: (text: Omit<TextType, "id">) => void;
|
addTextToActiveItem: (text: Omit<TextType, "id">) => void;
|
||||||
addButton: (
|
addButton: (
|
||||||
sectionKey: keyof PersonalityDataType,
|
sectionKey: string,
|
||||||
itemId: string,
|
itemId: string,
|
||||||
button: Omit<ButtonType, "id">
|
button: Omit<ButtonType, "id">
|
||||||
) => void;
|
) => void;
|
||||||
addButtonToActiveItem: (button: Omit<ButtonType, "id">) => void;
|
addButtonToActiveItem: (button: Omit<ButtonType, "id">) => void;
|
||||||
updateText: (
|
updateText: (
|
||||||
sectionKey: keyof PersonalityDataType,
|
sectionKey: string,
|
||||||
itemId: string,
|
itemId: string,
|
||||||
textId: string,
|
textId: string,
|
||||||
updates: Partial<TextType>
|
updates: Partial<TextType>
|
||||||
) => void;
|
) => void;
|
||||||
updateButton: (
|
updateButton: (
|
||||||
sectionKey: keyof PersonalityDataType,
|
sectionKey: string,
|
||||||
itemId: string,
|
itemId: string,
|
||||||
buttonId: string,
|
buttonId: string,
|
||||||
updates: Partial<ButtonType>
|
updates: Partial<ButtonType>
|
||||||
) => void;
|
) => void;
|
||||||
addImage: (
|
addImage: (
|
||||||
sectionKey: keyof PersonalityDataType,
|
sectionKey: string,
|
||||||
itemId: string,
|
itemId: string,
|
||||||
image: Omit<ImageType, "id">
|
image: Omit<ImageType, "id">
|
||||||
) => void;
|
) => void;
|
||||||
addImageToActiveItem: (image: Omit<ImageType, "id">) => void;
|
addImageToActiveItem: (image: Omit<ImageType, "id">) => void;
|
||||||
updateImage: (
|
updateImage: (
|
||||||
sectionKey: keyof PersonalityDataType,
|
sectionKey: string,
|
||||||
itemId: string,
|
itemId: string,
|
||||||
imageId: string,
|
imageId: string,
|
||||||
updates: Partial<ImageType>
|
updates: Partial<ImageType>
|
||||||
) => void;
|
) => void;
|
||||||
// Social network methods
|
// Social network methods
|
||||||
addSocial: (
|
addSocial: (
|
||||||
sectionKey: keyof PersonalityDataType,
|
sectionKey: string,
|
||||||
itemId: string,
|
itemId: string,
|
||||||
social: Omit<SocialType, "id">
|
social: Omit<SocialType, "id">
|
||||||
) => void;
|
) => void;
|
||||||
addSocialToActiveItem: (social: Omit<SocialType, "id">) => void;
|
addSocialToActiveItem: (social: Omit<SocialType, "id">) => void;
|
||||||
updateSocial: (
|
updateSocial: (
|
||||||
sectionKey: keyof PersonalityDataType,
|
sectionKey: string,
|
||||||
itemId: string,
|
itemId: string,
|
||||||
socialId: string,
|
socialId: string,
|
||||||
updates: Partial<SocialType>
|
updates: Partial<SocialType>
|
||||||
) => void;
|
) => void;
|
||||||
deleteSocial: (
|
deleteSocial: (sectionKey: string, itemId: string, socialId: string) => void;
|
||||||
sectionKey: keyof PersonalityDataType,
|
|
||||||
itemId: string,
|
|
||||||
socialId: string
|
|
||||||
) => void;
|
|
||||||
// New methods for deleting elements
|
// New methods for deleting elements
|
||||||
deleteText: (
|
deleteText: (sectionKey: string, itemId: string, textId: string) => void;
|
||||||
sectionKey: keyof PersonalityDataType,
|
deleteButton: (sectionKey: string, itemId: string, buttonId: string) => void;
|
||||||
itemId: string,
|
deleteImage: (sectionKey: string, itemId: string, imageId: string) => void;
|
||||||
textId: string
|
|
||||||
) => void;
|
|
||||||
deleteButton: (
|
|
||||||
sectionKey: keyof PersonalityDataType,
|
|
||||||
itemId: string,
|
|
||||||
buttonId: string
|
|
||||||
) => void;
|
|
||||||
deleteImage: (
|
|
||||||
sectionKey: keyof PersonalityDataType,
|
|
||||||
itemId: string,
|
|
||||||
imageId: string
|
|
||||||
) => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TemplateType = {
|
export type TemplateType = {
|
||||||
|
|||||||
@@ -505,14 +505,22 @@ export const exportPersonalityToHTML = async (
|
|||||||
} else {
|
} else {
|
||||||
// اگر هیچ محتوایی نداریم و content section است
|
// اگر هیچ محتوایی نداریم و content section است
|
||||||
console.log(
|
console.log(
|
||||||
"🔥 [ExportHTML] Empty content section detected, adding {{content}} placeholder"
|
"🔥 [ExportHTML] Empty content section detected, sectionHeight:",
|
||||||
|
sectionHeight
|
||||||
);
|
);
|
||||||
if (sectionHeight === "246px") {
|
if (sectionHeight === "246px") {
|
||||||
|
console.log(
|
||||||
|
"🔥 [ExportHTML] Adding {{content}} placeholder to empty content"
|
||||||
|
);
|
||||||
result += `<tr>
|
result += `<tr>
|
||||||
<td width="100%" height="230" style="padding: 20px; vertical-align: top; font-size: 15px; line-height: 1.8;">
|
<td width="100%" height="230" style="padding: 20px; vertical-align: top; font-size: 15px; line-height: 1.8;">
|
||||||
<div style="margin: 0; text-align: right; color: #888; font-size: 14px;">{{content}}</div>
|
<div style="margin: 0; text-align: right; color: #888; font-size: 14px;">{{content}}</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
"🔥 [ExportHTML] Not content section, skipping {{content}}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -567,18 +575,92 @@ export const exportPersonalityToHTML = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Helper function to determine section height
|
||||||
|
const getSectionHeight = (
|
||||||
|
sectionKey: string,
|
||||||
|
sectionData: PersonalityDataType[string]
|
||||||
|
) => {
|
||||||
|
console.log(`🔥 [ExportHTML] Getting height for section: ${sectionKey}`, {
|
||||||
|
isContent: sectionData?.isContent,
|
||||||
|
});
|
||||||
|
if (sectionKey === "content" || sectionData?.isContent) {
|
||||||
|
return "246px";
|
||||||
|
}
|
||||||
|
// header, footer و سکشنهای جدید همه 123px ارتفاع دارند
|
||||||
|
return "123px";
|
||||||
|
};
|
||||||
|
|
||||||
// Generate complete HTML exactly like Personality.tsx structure
|
// Generate complete HTML exactly like Personality.tsx structure
|
||||||
|
console.log("🔥 [ExportHTML] Available sections:", Object.keys(data));
|
||||||
|
console.log(
|
||||||
|
"🔥 [ExportHTML] Full data structure:",
|
||||||
|
JSON.stringify(data, null, 2)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Render all sections in the same order as they appear in the object
|
||||||
|
const sectionsHtml = Object.keys(data)
|
||||||
|
.map((sectionKey) => {
|
||||||
|
const sectionData = data[sectionKey];
|
||||||
|
const height = getSectionHeight(sectionKey, sectionData);
|
||||||
|
|
||||||
|
console.log(`🔥 [ExportHTML] Processing section: ${sectionKey}`, {
|
||||||
|
name: sectionData.name,
|
||||||
|
columnsCount: sectionData.columnsCount,
|
||||||
|
itemsLength: sectionData.items?.length || 0,
|
||||||
|
height: height,
|
||||||
|
isContent: sectionData.isContent,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pass only the section structure (without name and isContent)
|
||||||
|
const sectionStructure = {
|
||||||
|
columnsCount: sectionData.columnsCount,
|
||||||
|
items: sectionData.items || [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderedSection = renderSection(sectionStructure, height, "");
|
||||||
|
console.log(
|
||||||
|
`🔥 [ExportHTML] Section ${sectionKey} rendered, HTML length:`,
|
||||||
|
renderedSection.length
|
||||||
|
);
|
||||||
|
|
||||||
|
return renderedSection;
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
"🔥 [ExportHTML] Total sections rendered:",
|
||||||
|
Object.keys(data).length
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
"🔥 [ExportHTML] Combined sections HTML length:",
|
||||||
|
sectionsHtml.length
|
||||||
|
);
|
||||||
|
|
||||||
const html = `<div style="max-width: 600px; margin: 0 auto; direction: rtl;">
|
const html = `<div style="max-width: 600px; margin: 0 auto; direction: rtl;">
|
||||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse: collapse;">
|
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse: collapse;">
|
||||||
<tbody>
|
<tbody>
|
||||||
${renderSection(data.header, "123px")}
|
${sectionsHtml}
|
||||||
${renderSection(data.content, "246px", "")}
|
|
||||||
${renderSection(data.footer, "123px", "")}
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
console.log("🔥 [ExportHTML] Final HTML generated:", html);
|
console.log("🔥 [ExportHTML] Final HTML generated (length):", html.length);
|
||||||
|
console.log(
|
||||||
|
"🔥 [ExportHTML] HTML contains {{content}}:",
|
||||||
|
html.includes("{{content}}")
|
||||||
|
);
|
||||||
|
|
||||||
|
// نمایش قسمتهایی از HTML که شامل content است
|
||||||
|
const contentMatch = html.match(/<div[^>]*>{{content}}<\/div>/g);
|
||||||
|
if (contentMatch) {
|
||||||
|
console.log(
|
||||||
|
"🔥 [ExportHTML] Found {{content}} placeholders:",
|
||||||
|
contentMatch
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log("🔥 [ExportHTML] NO {{content}} placeholders found in HTML!");
|
||||||
|
}
|
||||||
|
|
||||||
return html;
|
return html;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user