bug fixes

This commit is contained in:
Deltora72 2026-05-27 14:07:41 +03:30
parent 1e28c604f3
commit 2c15ceb03f
6 changed files with 577 additions and 8 deletions

View File

@ -0,0 +1,159 @@
import { useGetRouter } from 'services/general/general';
import useTranslate from 'services/translation/translation';
import Modal from 'components/modal/modal';
import { FormGroup } from '../../general/fields';
import { useRef, useState } from 'react';
import { BillboardVitrine } from 'common/types/billboard';
import VitrineFormContainerShared from './vitrine-form-container-shared';
import VitrineFormGeneral from './vitrine-form-general';
interface AddNewVitrineSectionProps {
data: BillboardVitrine | null;
open: boolean;
themeStyles: React.CSSProperties;
onClose: () => void;
}
const AddNewVitrineSection: React.FunctionComponent<AddNewVitrineSectionProps> = ({ data, open = false, themeStyles, onClose }) => {
const { locale, query } = useGetRouter();
// states
const [sectionOpen, setSectionOpen] = useState({
container_settings: false,
card_settings: false,
});
// variables
const translate = useTranslate();
const formData = useRef({
"content": {
"id": 3,
"type": "products",
"selection": "category",
"catId": "16",
"categories_ids": [],
"custom_selection": [],
"sort": "descending",
"count": 8,
"order": 2
},
"container": {
"type": "slider",
"desktop_col_span": 4,
"mobile_col_span": 2,
"title": {
"fa": "لباس های تابستانی",
"en": "summer dresses"
},
"link_text": {
"fa": "مشاهده همه",
"en": "see all"
},
"no_header": false,
"slider": {
"gap": 2,
"free_slide": true,
"auto_play": false,
"duration": 3000,
"loop": false,
"arrows": false,
"dots": false
}
},
"card": {
"type": "product-v-one",
"size": "sm"
}
});
// methods
const handleOpenSections = (type: string, open: boolean) => {
setSectionOpen(prev => (
{
container_settings: false,
card_settings: false,
[type]: open
}
))
}
const handleUpdate = (type: "container_shared" | "form_general", value: any) => {
}
const handleClose = () => {
onClose();
}
// useEffects
// useEffect(() => {
// }, []);
// return
return (
<Modal
header={true}
wrapperId={`add-new-vitrine-section-modal`}
open={open}
onClose={handleClose}
title={translate("dashboard-billboard-vitrine-add-new-cta")}
className="flex flex-col bg-white w-[90vw] h-auto max-h-[80vh] lg:w-125 lg:h-auto lg:min-w-125 lg:max-h-[90vh] rounded-sm"
childrenClass="hide-scrollbar overflow-x-hidden rounded-sm"
headerClass="h-12!"
titleClass="text-sm!"
closeClass="size-6!"
style={themeStyles}
>
<div className="py-4 px-6">
{/* general form */}
<VitrineFormGeneral
data={{
title: {
fa: data ? data.container.title.fa : "",
en: data ? data.container.title.fa : "",
},
itemsType: data ? data.content.type : "products",
selectionCriteria: data ? data.content.selection : "date",
containerTypes: data ? data.container.type : "grid",
sortDirection: data ? data.content.sort : "descending",
itemsCount: data ? data.content.count : 8,
}}
onChange={(data) => handleUpdate("form_general", data)}
/>
{/* container shared details */}
<FormGroup
title={translate("dashboard-billboard-vitrine-general-details")}
open={sectionOpen.container_settings}
onChange={(open) => handleOpenSections("container_settings", open)}
className={``}
childrenClass="grid grid-cols-2 gap-x-4 gap-y-0! p-1! pt-4!"
>
<VitrineFormContainerShared
data={{
desktop_col_span: data ? data.container.desktop_col_span : 4,
mobile_col_span: data ? data.container.mobile_col_span : 2,
no_header: data ? data.container.no_header : false,
link_text: {
fa: data ? data.container.link_text.fa : "",
en: data ? data.container.link_text.en : "",
},
wrapperClass: data ? data.container.wrapperClass : "",
headerClass: data ? data.container.headerClass : "",
titleClass: data ? data.container.titleClass : "",
linkClass: data ? data.container.linkClass : "",
childrenWrapperClass: data ? data.container.childrenWrapperClass : "",
childrenClass: data ? data.container.childrenClass : "",
}}
onChange={(data) => handleUpdate("container_shared", data)}
/>
</FormGroup>
</div>
</Modal>
)
}
export default AddNewVitrineSection

View File

@ -31,6 +31,7 @@ import VCategoriesContainer from 'common/templates/billboard/page/vitrine/contai
import VSeparatorContainer from 'common/templates/billboard/page/vitrine/containers/v-separator-container';
import Modal from 'components/modal/modal';
import EventAlert from 'components/popover/event-alert';
import AddNewVitrineSection from './add-new-vitrine-section';
interface ManageVitrineProps {
billboard: Billboard;
@ -51,6 +52,7 @@ const ManageVitrine: React.FunctionComponent<ManageVitrineProps> = ({ billboard,
const [alertOpen, setAlertOpen] = useState(false);
const [newSectionOpen, setNewSectionOpen] = useState(false);
const [activeSectionId, setActiveSectionId] = useState(null);
const [selectedVitrineItem, setSelectedVitrineItem] = useState<BillboardVitrine | null>(null);
const [updating, setUpdating] = useState(false);
// variables
@ -143,8 +145,6 @@ const ManageVitrine: React.FunctionComponent<ManageVitrineProps> = ({ billboard,
onClose={onAlertClose}
hasTime
/>
{/* */}
<Button
type="button"
text={translate("dashboard-billboard-vitrine-main-title")}
@ -158,7 +158,7 @@ const ManageVitrine: React.FunctionComponent<ManageVitrineProps> = ({ billboard,
wrapperId={`categories-management-modal`}
open={open}
onClose={handleClose}
title={translate("dashboard-product-page-product-categories")}
title={translate("dashboard-billboard-vitrine-main-title")}
className="flex flex-col bg-white w-[90vw] h-auto max-h-[80vh] lg:h-auto lg:min-w-125 lg:w-[50vw] lg:max-h-[90vh] rounded-sm"
childrenClass="hide-scrollbar overflow-x-hidden rounded-sm"
headerClass="h-12!"
@ -222,7 +222,13 @@ const ManageVitrine: React.FunctionComponent<ManageVitrineProps> = ({ billboard,
onClick={handleClose}
/>
</div>
</Modal >
</Modal>
<AddNewVitrineSection
data={selectedVitrineItem}
open={newSectionOpen}
themeStyles={themeStyles}
onClose={() => setNewSectionOpen(false)}
/>
</BillboardSection>
)
}

View File

@ -0,0 +1,124 @@
import { hasValue, safeClone, useGetRouter } from 'services/general/general';
import useTranslate from 'services/translation/translation';
import { BillboardFormItem } from '../../general/fields';
import { formInputClass } from 'components/general/form-tools';
import Input from 'components/input/text';
import { useRef, useState } from 'react';
interface DataProps {
desktop_col_span: 1 | 2 | 3 | 4;
mobile_col_span: 1 | 2;
no_header: boolean;
link_text: {
fa: string;
en: string;
};
wrapperClass: string;
headerClass: string;
titleClass: string;
linkClass: string;
childrenWrapperClass: string;
childrenClass: string;
}
interface VitrineFormContainerSharedProps {
data: DataProps;
onChange: (data: DataProps) => void;
}
const VitrineFormContainerShared: React.FunctionComponent<VitrineFormContainerSharedProps> = ({ data, onChange }) => {
const { locale, query } = useGetRouter();
// states
const [fieldErrors, setFieldErrors] = useState({
name: false,
});
// variables
const translate = useTranslate();
const formErrors = {
necessaryFields: translate("all-fields-necessary-error"),
postalCode: translate("postal-code-format-error"),
phoneNumber: translate("phone-number-format-error"),
}
const formData = useRef({
link_text: {
fa: data.link_text.fa,
en: data.link_text.en,
},
desktop_col_span: data.desktop_col_span,
mobile_col_span: data.mobile_col_span,
});
// methods
const handleFormUpdate = (name: string, value: any) => {
formData.current = { ...formData.current, [name]: value };
}
const validate = () => {
const localErrors = safeClone(fieldErrors);
// (!hasValue(formData.current.name.fa) && !hasValue(formData.current.name.en)) ? localErrors.name = true : localErrors.name = false;
setFieldErrors({ ...fieldErrors, ...localErrors });
if (!Object.values(localErrors).some(x => x === true)) {
// handleMethodSubmition();
}
}
// useEffects
// useEffect(() => {
// }, []);
// return
return (
<>
{/* desktop width (col-span) */}
<BillboardFormItem
label={{
forId: "desktop-width",
title: translate("dashboard-billboard-vitrine-desktop-width"),
className: "text-secondary-light! font-semibold px-1"
}}
wrapperClass="flex flex-col justify-between pb-4 col-span-1"
>
<Input
type="number"
value={formData.current.desktop_col_span}
placeholder={translate("dashboard-billboard-vitrine-items-count-placeholder")}
inputDelay={300}
min={1}
max={4}
onInput={data => handleFormUpdate("desktop_width", data)}
className={`${formInputClass}`}
wrapperClass="w-full mt-1"
/>
</BillboardFormItem>
{/* mobile width (col-span) */}
<BillboardFormItem
label={{
forId: "mobile-width",
title: translate("dashboard-billboard-vitrine-mobile-width"),
className: "text-secondary-light! font-semibold px-1"
}}
wrapperClass="flex flex-col justify-between pb-4 col-span-1"
>
<Input
type="number"
value={formData.current.mobile_col_span}
placeholder={translate("dashboard-billboard-vitrine-items-count-placeholder")}
inputDelay={300}
min={1}
max={2}
onInput={data => handleFormUpdate("mobile_width", data)}
className={`${formInputClass}`}
wrapperClass="w-full mt-1"
/>
</BillboardFormItem>
</>
)
}
export default VitrineFormContainerShared

View File

@ -0,0 +1,280 @@
import { hasValue, safeClone, useGetRouter } from 'services/general/general';
import useTranslate from 'services/translation/translation';
import { BillboardFormItem } from '../../general/fields';
import { formInputClass, TranslatableFieldsInfo } from 'components/general/form-tools';
import Input from 'components/input/text';
import { useRef, useState } from 'react';
import { BillboardVitrineSelection } from 'common/types/billboard';
import Select from 'components/select/select';
type itemTypes = "products" | "banner" | "product_categories" | "separator" | "news" | "blog";
type containerTypes = "grid" | "slider" | "slideshow" | "categories" | "separator";
interface DataProps {
title: {
fa: string;
en: string;
};
itemsType: itemTypes;
selectionCriteria: BillboardVitrineSelection;
containerTypes: containerTypes;
sortDirection: "ascending" | "descending";
itemsCount: number;
}
interface VitrineFormGeneralProps {
data: DataProps;
onChange: (data: DataProps) => void;
}
const VitrineFormGeneral: React.FunctionComponent<VitrineFormGeneralProps> = ({ data, onChange }) => {
const { locale, query } = useGetRouter();
// states
const [fieldsTr, setFieldsTr] = useState(locale);
const [itemsType, setItemsType] = useState<itemTypes>("products");
const [selectionCriteria, setSelectionCriteria] = useState<BillboardVitrineSelection>("date");
const [containerType, setContainerType] = useState<containerTypes>("grid");
const [sortDirection, setSortDirection] = useState<"ascending" | "descending">("descending");
const [fieldErrors, setFieldErrors] = useState({
name: false,
});
// variables
const translate = useTranslate();
const formErrors = {
necessaryFields: translate("all-fields-necessary-error"),
postalCode: translate("postal-code-format-error"),
phoneNumber: translate("phone-number-format-error"),
}
const formData = useRef({
name: {
fa: data ? data.title.fa : "",
en: data ? data.title.en : "",
},
items_count: data ? data.itemsCount : 8,
});
// methods
const handleItemsTypeSelection = (type: itemTypes) => {
setItemsType(type);
setContainerType(getAvailableContainerTypes(type)[0]);
}
const getAvailableContainerTypes = (contentType: itemTypes) => {
let availableTypes: containerTypes[] = [];
switch (contentType) {
case "products":
availableTypes.push("grid", "slider");
break;
case "banner":
availableTypes.push("grid");
break;
case "product_categories":
availableTypes.push("categories");
break;
case "separator":
availableTypes.push("separator");
break;
default:
break;
}
return availableTypes;
}
const getAvailableSelectionTypes = (contentType: itemTypes) => {
let availableTypes: BillboardVitrineSelection[] = [];
switch (contentType) {
case "products":
availableTypes.push("date", "category", "featured", "ratings", "visits", "custom");
break;
case "banner":
availableTypes.push("date", "custom");
break;
case "product_categories":
availableTypes.push("date", "custom");
break;
case "separator":
availableTypes.push("date");
break;
default:
break;
}
return availableTypes;
}
const handleFormUpdate = (name: string, value: any) => {
formData.current = { ...formData.current, [name]: value };
onChange({
title: formData.current.name,
itemsType: itemsType,
selectionCriteria: selectionCriteria,
containerTypes: containerType,
sortDirection: sortDirection,
itemsCount: formData.current.items_count,
})
}
const validate = () => {
const localErrors = safeClone(fieldErrors);
(!hasValue(formData.current.name.fa) && !hasValue(formData.current.name.en)) ? localErrors.name = true : localErrors.name = false;
setFieldErrors({ ...fieldErrors, ...localErrors });
if (!Object.values(localErrors).some(x => x === true)) {
// handleMethodSubmition();
}
}
// useEffects
// useEffect(() => {
// }, []);
// return
return (
<>
<TranslatableFieldsInfo onLangSwitch={setFieldsTr} wrapperClass="mb-4" textClass="text-xs/6!" />
{/* name */}
<BillboardFormItem
label={{
forId: "name",
title: translate("dashboard-billboard-vitrine-section-title"),
className: "text-secondary-light! font-semibold px-1"
}}
wrapperClass="flex flex-col justify-between pb-4 col-span-2"
required
errorText={formErrors.necessaryFields}
errorVisible={fieldErrors.name}
translatable
>
<Input
type="text"
value={fieldsTr === "fa" ? formData.current.name.fa : formData.current.name.en}
placeholder={translate("dashboard-billboard-vitrine-section-title-placeholder")}
stripeHtml
inputDelay={300}
onInput={data => handleFormUpdate("name", fieldsTr === "fa" ? { fa: data, en: formData.current.name.en } : { fa: formData.current.name.fa, en: data })}
className={`${formInputClass} ${fieldsTr === "en" ? "[direction:ltr]" : ""}`}
wrapperClass="w-full mt-1"
/>
</BillboardFormItem>
{/* type */}
<BillboardFormItem
label={{
forId: "item-type",
title: translate("dashboard-billboard-vitrine-item-type"),
className: "text-secondary-light! font-semibold px-1"
}}
wrapperClass="flex flex-col justify-between pb-4"
>
<Select
id="item-type"
value={{ label: translate(`dashboard-billboard-vitrine-type-${itemsType.replace(/_/g, '-')}`), value: itemsType, disabled: false }}
options={["products", "banner", "product_categories", "separator"].map((x: string) => ({ label: translate(`dashboard-billboard-vitrine-type-${x.replace(/_/g, '-')}`), value: x, disabled: false }))}
onChange={(data: any) => handleItemsTypeSelection(data.value)}
optionsWrapper="w-full px-2 rounded-lg bg-white capitalize text-sm py-2 lg:cursor-pointer"
optionClass="text-xs!"
buttonWrapper="ring-0! bg-white"
buttonText="text-sm!"
wrapperClass="block bg-market-input rounded-xl py-1 px-1"
/>
</BillboardFormItem>
{/* selection criteria */}
<BillboardFormItem
label={{
forId: "selection-criteria",
title: translate("dashboard-billboard-vitrine-selection-criteria"),
className: "text-secondary-light! font-semibold px-1"
}}
wrapperClass="flex flex-col justify-between pb-4"
>
<Select
id="selection-criteria"
value={{ label: translate(`dashboard-billboard-vitrine-selection-type-${selectionCriteria.replace(/_/g, '-')}`), value: selectionCriteria, disabled: false }}
options={getAvailableSelectionTypes(itemsType).map((x: string) => ({ label: translate(`dashboard-billboard-vitrine-selection-type-${x.replace(/_/g, '-')}`), value: x, disabled: false }))}
onChange={(data: any) => handleItemsTypeSelection(data.value)}
optionsWrapper="w-full px-2 rounded-lg bg-white capitalize text-sm py-2 lg:cursor-pointer"
optionClass="text-xs!"
buttonWrapper="ring-0! bg-white"
buttonText="text-sm!"
wrapperClass="block bg-market-input rounded-xl py-1 px-1"
/>
</BillboardFormItem>
{/* sort direction */}
<BillboardFormItem
label={{
forId: "sort-direction",
title: translate("dashboard-billboard-vitrine-sort-direction"),
className: "text-secondary-light! font-semibold px-1"
}}
wrapperClass="flex flex-col justify-between pb-4"
>
<Select
id="sort-direction"
value={{ label: translate(`dashboard-billboard-vitrine-sort-${sortDirection.replace(/_/g, '-')}`), value: sortDirection, disabled: false }}
options={["ascending", "descending"].map((x: string) => ({ label: translate(`dashboard-billboard-vitrine-sort-${x.replace(/_/g, '-')}`), value: x, disabled: false }))}
onChange={(data: any) => setSortDirection(data.value)}
optionsWrapper="w-full px-2 rounded-lg bg-white capitalize text-sm py-2 lg:cursor-pointer"
optionClass="text-xs!"
buttonWrapper="ring-0! bg-white"
buttonText="text-sm!"
wrapperClass="block bg-market-input rounded-xl py-1 px-1"
/>
</BillboardFormItem>
{/* container type */}
<BillboardFormItem
label={{
forId: "container-type",
title: translate("dashboard-billboard-vitrine-container-type"),
className: "text-secondary-light! font-semibold px-1"
}}
wrapperClass="flex flex-col justify-between pb-4"
>
<Select
id="container-type"
value={{ label: translate(`dashboard-billboard-vitrine-container-type-${containerType.replace(/_/g, '-')}`), value: containerType, disabled: false }}
options={getAvailableContainerTypes(itemsType).map(x => ({ label: translate(`dashboard-billboard-vitrine-container-type-${x.replace(/_/g, '-')}`), value: x, disabled: false }))}
onChange={(data: any) => setContainerType(data.value)}
optionsWrapper="w-full px-2 rounded-lg bg-white capitalize text-sm py-2 lg:cursor-pointer"
optionClass="text-xs!"
buttonWrapper="ring-0! bg-white"
buttonText="text-sm!"
wrapperClass="block bg-market-input rounded-xl py-1 px-1"
/>
</BillboardFormItem>
{/* items count */}
<BillboardFormItem
label={{
forId: "items-count",
title: translate("dashboard-billboard-vitrine-items-count"),
className: "text-secondary-light! font-semibold px-1"
}}
wrapperClass="flex flex-col justify-between pb-4 col-span-2"
>
<Input
type="number"
value={formData.current.items_count}
placeholder={translate("dashboard-billboard-vitrine-items-count-placeholder")}
inputDelay={300}
min={1}
max={20}
onInput={data => handleFormUpdate("items_count", data)}
className={`${formInputClass} ${fieldsTr === "en" ? "[direction:ltr]" : ""}`}
wrapperClass="w-full mt-1"
/>
</BillboardFormItem>
</>
)
}
export default VitrineFormGeneral

View File

@ -165,7 +165,7 @@ const ProductDetails: React.FunctionComponent<ProductDetailsProps> = ({ billboar
}
<LinkToProduct billboardTitle={billboardTitle} query={query} translate={translate} className="max-2xl:hidden" />
</div>
<h1 className="block text-base/7 font-extrabold lg:text-lg text-secondary-light mb-4 capitalize line-clamp-1">{productTitle}</h1>
<h1 className="block text-base/7 font-extrabold lg:text-lg/9 text-secondary-light mb-4 capitalize line-clamp-1">{productTitle}</h1>
<p className="text-xs/6 lg:text-xs/6 line-clamp-1">{stripHtml(productDescription)}</p>
<div className="flex items-center gap-2 mb-4">
<span className="flex productDetailss-center w-max text-xs/4 bg-gray-100 text-secondary-light py-1 px-2 rounded-sm mt-4 font-extrabold">{"#" + product.product_id}</span>

View File

@ -113,17 +113,17 @@ export type BillboardVitrine = {
content: BillboardVitrineContent;
container: {
type: "grid" | "slider" | "slideshow" | "categories" | "separator";
desktop_col_span: 1 | 2 | 3 | 4;
mobile_col_span: 1 | 2;
title: {
fa: string;
en: string;
};
desktop_col_span: 1 | 2 | 3 | 4;
mobile_col_span: 1 | 2;
no_header: boolean;
link_text: {
fa: string;
en: string;
};
no_header: boolean;
wrapperClass: string;
headerClass: string;
titleClass: string;