Update | Project Ready

This commit is contained in:
Possible
2025-04-03 17:05:59 +01:00
commit c5aee9e2ec
176 changed files with 34486 additions and 0 deletions
@@ -0,0 +1,85 @@
import React, { useCallback, useState } from 'react'
// import { GrayMaterial, PerforatedMaterial, WoodgrainMaterial } from 'Assets/images'
// import { MaterialType } from 'Utils/constants'
import { CopyIcon, HandIcon, PasteIcon, PrinterIcon, ReverseLeftIcon, ReverseRightIcon, TrashIcon } from 'Assets/svgs'
import { CanvasModes } from 'Utils/constants'
export const ActionButtons = ( { className, onRedoClick, onUndoClick, onCopy, onPaste, onDeleteSelection, onPrintScreen } ) => {
const [ mode, setMode ] = useState( CanvasModes.Still )
// const [ activeMaterial, setActiveMaterial ] = useState( MaterialType.Gray )
// const changeMode = useCallback( () => {
// if ( mode === CanvasModes.Pan ) {
// setMode( CanvasModes.Still )
// onCanvasModeChange( CanvasModes.Still )
// } else if ( mode === CanvasModes.Still ) {
// setMode( CanvasModes.Pan )
// onCanvasModeChange( CanvasModes.Pan )
// }
// }, [ mode ] )
return (
<div className={ `flex gap-x-[8px] bg-transparent border-none ${ className }` }>
<button onClick={ () => onPrintScreen() }>
<div
className={ `flex justify-center items-center px-[9px] py-[8px] bg-[#ffffff14] border-2 border-[#404968] rounded cursor-pointer w-[42px] h-[40px]` }
>
<PrinterIcon />
</div>
</button>
{/* <button onClick={ changeMode }>
<div
className={ `flex justify-center items-center px-[9px] py-[8px] bg-[#ffffff14] border-2 border-[#404968] rounded cursor-pointer w-[42px] h-[40px]` }
>
<HandIcon />
</div>
</button> */}
<button onClick={ onPaste }>
<div
className={ `flex justify-center items-center px-[9px] py-[8px] bg-[#ffffff14] border-2 border-[#404968] rounded cursor-pointer w-[42px] h-[40px]` }
>
<PasteIcon />
</div>
</button>
<button onClick={ onCopy }>
<div
className={ `flex justify-center items-center px-[9px] py-[8px] bg-[#ffffff14] border-2 border-[#404968] rounded cursor-pointer w-[42px] h-[40px]` }
>
<CopyIcon />
</div>
</button>
<button onClick={ onUndoClick }>
<div
className={ `flex justify-center items-center px-[9px] py-[8px] bg-[#ffffff14] border-2 border-[#404968] rounded cursor-pointer w-[42px] h-[40px]` }
>
<ReverseLeftIcon />
</div>
</button>
<button onClick={ onRedoClick }>
<div
className={ `flex justify-center items-center px-[9px] py-[8px] bg-[#ffffff14] border-2 border-[#404968] rounded cursor-pointer w-[42px] h-[40px]` }
>
<ReverseRightIcon />
</div>
</button>
<button onClick={ onDeleteSelection }>
<div
className={ `flex justify-center items-center px-[9px] py-[8px] bg-[#ffffff14] border-2 border-[#404968] rounded cursor-pointer w-[42px] h-[40px]` }
>
<TrashIcon />
</div>
</button>
</div>
)
}
+1
View File
@@ -0,0 +1 @@
export { ActionButtons } from './ActionButtons'
+16
View File
@@ -0,0 +1,16 @@
import React from "react";
import { NavLink } from "react-router-dom";
const AddButton = ({ link }) => {
return (
<>
<NavLink to={link} className="w-7 h-7 text-center center-svg text-white font-bold bg-blue-500 hover:bg-blue-700 flex">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-plus" viewBox="0 0 16 16">
{" "}
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z" />{" "}
</svg>
</NavLink>
</>
);
};
export default AddButton;
+205
View File
@@ -0,0 +1,205 @@
import React from "react";
import { AuthContext } from "../authContext";
import { NavLink } from "react-router-dom";
import { GlobalContext } from "../globalContext";
export const AdminHeader = () => {
const { dispatch } = React.useContext(AuthContext);
const { state } = React.useContext(GlobalContext);
return (
<>
<div className={`sidebar-holder ${!state.isOpen ? "open-nav" : ""}`}>
<div className="sticky top-0 h-fit">
<div className="w-full p-4 bg-sky-500">
<div className="text-white font-bold text-center text-2xl">
Admin
</div>
</div>
<div className="w-full sidebar-list">
<ul className="flex flex-wrap">
<li className="list-none block w-full">
<NavLink
to="/admin/dashboard"
className={`${
state.path == "admin" ? "text-black bg-gray-200" : ""
}`}
>
Dashboard
</NavLink>
</li>
<li className="list-none block w-full">
<NavLink
to="/admin/accessories"
className={`${
state.path == "accessories" ? "text-black bg-gray-200" : ""
}`}
>
Accessories
</NavLink>
</li>
<li className="list-none block w-full">
<NavLink
to="/admin/email"
className={`${
state.path == "email" ? "text-black bg-gray-200" : ""
}`}
>
Email
</NavLink>
</li>
<li className="list-none block w-full">
<NavLink
to="/admin/photo"
className={`${
state.path == "photo" ? "text-black bg-gray-200" : ""
}`}
>
Photo
</NavLink>
</li>
<li className="list-none block w-full">
<NavLink
to="/admin/boat_lifts"
className={`${
state.path == "boat_lifts" ? "text-black bg-gray-200" : ""
}`}
>
Boat Lifts
</NavLink>
</li>
<li className="list-none block w-full">
<NavLink
to="/admin/dealers"
className={`${
state.path == "dealers" ? "text-black bg-gray-200" : ""
}`}
>
Dealers
</NavLink>
</li>
<li className="list-none block w-full">
<NavLink
to="/admin/docks"
className={`${
state.path == "docks" ? "text-black bg-gray-200" : ""
}`}
>
Docks
</NavLink>
</li>
<li className="list-none block w-full">
<NavLink
to="/admin/cms"
className={`${
state.path == "cms" ? "text-black bg-gray-200" : ""
}`}
>
Cms
</NavLink>
</li>
<li className="list-none block w-full">
<NavLink
to="/admin/instructions"
className={`${
state.path == "instructions" ? "text-black bg-gray-200" : ""
}`}
>
Instructions
</NavLink>
</li>
<li className="list-none block w-full">
<NavLink
to="/admin/user"
className={`${
state.path == "user" ? "text-black bg-gray-200" : ""
}`}
>
User
</NavLink>
</li>
<li className="list-none block w-full">
<NavLink
to="/admin/quotes"
className={`${
state.path == "quotes" ? "text-black bg-gray-200" : ""
}`}
>
Quotes
</NavLink>
</li>
<li className="list-none block w-full">
<NavLink
to="/admin/wedges"
className={`${
state.path == "wedges" ? "text-black bg-gray-200" : ""
}`}
>
Wedges
</NavLink>
</li>
<li className="list-none block w-full">
<NavLink
to="/admin/ramps"
className={`${
state.path == "ramps" ? "text-black bg-gray-200" : ""
}`}
>
Ramps
</NavLink>
</li>
<li className="list-none block w-full">
<NavLink
to="/admin/quotes_mail_recipients"
className={`${
state.path == "quotes_mail_recipients"
? "text-black bg-gray-200"
: ""
}`}
>
Quotes Mail Recipients
</NavLink>
</li>
<li className="list-none block w-full">
<NavLink
to="/admin/profile"
className={`${
state.path == "profile" ? "text-black bg-gray-200" : ""
}`}
>
Profile
</NavLink>
</li>
<li className="list-none block w-full">
<NavLink
to="/admin/login"
onClick={() =>
dispatch({
type: "LOGOUT",
})
}
>
Logout
</NavLink>
</li>
</ul>
</div>
</div>
</div>
</>
);
};
export default AdminHeader;
@@ -0,0 +1,82 @@
import React, { memo, useState, useCallback } from 'react';
import { Modal } from 'Components/Modal';
import { InteractiveButton } from 'Components/InteractiveButton';
// import { ArrowLeftIcon, ArrowRrightIcon, GreenTickIcon } from 'Assets/svgs';
// import { dock_image, EstimateSteps, Tables, Truthy } from 'Utils/constants';
// import { ContactInformation } from 'Components/ContactInformation';
// import { LakeSurroundings } from 'Components/LakeSurroundings';
// import { Comments } from 'Components/Comments';
import MkdSDK from 'Utils/MkdSDK';
const sdk = new MkdSDK()
const classes = {
modal: 'string',
modalDialog: 'relative bg-white w-[500px]',
modalContent: 'string',
modalHeader: 'string',
modalTitle: 'string',
modalBody: 'string',
modalFooter: 'string',
closeButtonClass: 'string',
saveButtonClass: 'string',
}
const BuildCanvasFromLocalModal = ( {
showBuildCanvasFromLocalModal,
modalCloseClick,
editor
} ) => {
// const [ step, setStep ] = useState( EstimateSteps.ContactInformation )
// const [ submitLoading, setSubmitLoading ] = useState( false )
// const [ errorMessage, setErrorMessage ] = useState( null )
// const [ hasDealer, setHasDealer ] = useState( Truthy.False )
const onConfirm = useCallback( () => {
const json = JSON.parse( localStorage.getItem( "dock" ) )
editor.loadFromJSON( json, () => {
editor.renderAll()
}, ( o ) => {
// console.log( o )
} )
modalCloseClick()
}, [ editor ] )
return (
<Modal
title="CONTINUE FROM LAST SAVE"
isOpen={ showBuildCanvasFromLocalModal }
classes={ classes }
modalHeader
modalCloseClick={ modalCloseClick }
>
<div>
<div className={ `my-3` }>
Would You Like to Continue Building Dock From Last Save?
</div>
<div className={ `w-full flex gap-x-2 justify-between items-center` }>
<button
className={ `h-[44px] w-full rounded border-2 border-[#0F75BC] flex justify-center gap-x-2 items-center bg-[#0F75BC] text-[#ffffff] uppercase font-medium tracking-wide leading-[24px] text-[16px]` }
onClick={ modalCloseClick }
>No
</button>
<button
className={ `h-[44px] w-full rounded border-2 border-[#0F75BC] flex justify-center gap-x-2 items-center bg-[#0F75BC] text-[#ffffff] uppercase font-medium tracking-wide leading-[24px] text-[16px]` }
onClick={ onConfirm }
>Yes
</button>
</div>
</div>
</Modal>
);
}
const BuildCanvasFromLocalModalMemo = memo( BuildCanvasFromLocalModal );
export { BuildCanvasFromLocalModalMemo as BuildCanvasFromLocalModal };
@@ -0,0 +1 @@
export { BuildCanvasFromLocalModal } from './BuildCanvasFromLocalModal'
+758
View File
@@ -0,0 +1,758 @@
import React, { useCallback, useEffect, useState } from "react";
// import { Tabs } from 'Components/Tabs'
// import { TabNames } from 'Utils'
import { fabric } from "fabric";
import {
GrayMaterial,
PerforatedMaterial,
WoodgrainMaterial
} from "Assets/images";
import {
DockPanelCategories,
MaterialType,
DockPanelCategoryMap,
CylinderType
} from "Utils/constants";
import { Chevron } from "Assets/svgs";
import MkdSDK from "Utils/MkdSDK";
import { oneFeet, scaleFactor, Tables } from "Utils/constants";
import {
getCategory,
getMaterial,
getRampsCategory,
getWedgesAndRampsMaterial,
getWedgesCategory
} from "Utils/utils";
const sdk = new MkdSDK();
export const Builder = ({ editor }) => {
// let scaleFactor = 0.2;
const [activeMaterial, setActiveMaterial] = useState(MaterialType.Gray);
const [activeDockCategory, setActiveDockCategory] = useState(
DockPanelCategories.RollIn
);
const rampsInitialState = {
data: []
};
// const [ramps, setRamps] = useState(null);
const [activeLiftRange, setActiveLiftRange] = useState(null);
const [dock, setDock] = useState([]);
const [docks, setDocks] = useState([]);
const [accessories, setAccessories] = useState([]);
const [boatlifts, setBoatlifts] = useState([]);
const [wedgesAndRamps, setWedgesAndRamps] = useState({
wedges: [],
ramps: [],
selectedRamps: [],
selectedWedges: []
});
const [boatLift, setBoatLift] = useState([]);
const [liftRanges, setLiftRanges] = useState([]);
const [left, setLeft] = useState(300);
// const [ rollinDock, setRollinDock ] = useState( null )
// const [ floatingDock, setFloatingDock ] = useState( null )
// const [ sectionalDock, setSectionalDock ] = useState( null )
const onMaterialClick = useCallback(
(material) => {
setActiveMaterial(material);
},
[activeMaterial]
);
const onDockSelect = useCallback((dock) => {
if (!editor) {
return;
}
const editorHeight = editor.getHeight();
const division = editorHeight / oneFeet - 4;
let imageTopViewURL;
let materials;
let category;
if (["wedges", "ramps"].includes(dock?.type)) {
imageTopViewURL = (dock?.top_view).replace("%20", "+");
materials = getWedgesAndRampsMaterial(dock?.material);
} else {
imageTopViewURL = dock?.top_view;
materials = getMaterial(dock?.materials);
}
if (["ramps"].includes(dock?.type)) {
category = getRampsCategory(dock?.category);
} else if (["wedges"].includes(dock?.type)) {
category = getWedgesCategory(dock?.category);
} else {
category = getCategory(dock?.category);
}
const dockData = {
itemName: activeDockCategory,
image: dock?.image,
category: category,
length: dock?.length,
materials: materials,
top_view: dock?.top_view,
width: dock?.width,
lift_range: dock?.lift_range,
model: dock?.model,
no_of_cylinders: dock?.no_of_cylinders,
name: dock?.name,
thumbnail: dock?.thumbnail,
weight_capacity: dock?.weight_capacity
};
// TODO: Add dock to editor
// TODO: object which is the image should have the dockData, snapAngle of 45, snapThreshold of 5
// TODO: image should be scaled down by scaleFactor
// TODO: image should be positioned at the top left of the editor
// TODO: image should be added to the editor
// TODO: render the editor
}, []);
const getItems = useCallback((table) => {
// console.log( category, materials );
(async () => {
try {
// sdk.setTable( table );
const result = await sdk.getItems(table);
// console.log( result )
switch (table) {
case Tables.Docks:
return setDocks(() =>
result?.model
? [...result?.model.map((item) => ({ ...item, type: "docks" }))]
: []
);
case Tables.Boat_lifts:
// return console.log( result )
const liftRanges = result?.model
.filter((boatLift, index, self) => {
return (
index ===
self.findIndex(
(selfItem) => selfItem.lift_range === boatLift.lift_range
)
);
})
.map((item) => item.lift_range);
setLiftRanges(liftRanges.sort());
setActiveLiftRange(liftRanges.sort()[0]);
return setBoatlifts(() =>
result?.model
? [
...result?.model.map((item) => ({
...item,
type: "boatlifts"
}))
]
: []
);
case Tables.Accessories:
// return console.log( result )
return setAccessories(() =>
result?.model
? [
...result?.model.map((item) => ({
...item,
type: "accessories"
}))
]
: []
);
case Tables.Wedges:
// return console.log( result )
return setWedgesAndRamps((prev) => ({
...prev,
wedges: result?.model
? [
...result?.model.map((item) => ({
...item,
type: "wedges"
}))
]
: []
}));
case Tables.Ramps:
// return console.log( result )
return setWedgesAndRamps((prev) => ({
...prev,
ramps: result?.model
? [...result?.model.map((item) => ({ ...item, type: "ramps" }))]
: []
}));
// return setRamps(() => [...result?.model]);
}
} catch (error) {
console.log(error.message);
}
})();
}, []);
const onDockPanelClick = useCallback(
(dockCategory) => {
if (dockCategory !== activeDockCategory) {
setActiveDockCategory(dockCategory);
} else {
setActiveDockCategory("");
}
},
[activeDockCategory]
);
useEffect(() => {
if (!docks.length) {
// console.log( "I ran Docks" )
getItems(Tables.Docks);
}
if (!accessories.length) {
// console.log( "I ran Accessories" )
getItems(Tables.Accessories);
}
if (!wedgesAndRamps?.wedges?.length) {
// console.log( "I ran Wedges" )
getItems(Tables.Wedges);
}
if (!wedgesAndRamps?.ramps?.length) {
// console.log( "I ran Ramps" )
getItems(Tables.Ramps);
}
if (!boatlifts.length) {
// console.log( "I ran Boat_lifts" )
getItems(Tables.Boat_lifts);
}
}, [docks, accessories, boatlifts]);
useEffect(() => {
if (
[
DockPanelCategories.RollIn,
DockPanelCategories.Floating,
DockPanelCategories.Sectional
].includes(activeDockCategory)
) {
// console.log( activeDockCategory, activeMaterial )
const category =
activeDockCategory === DockPanelCategories.RollIn
? DockPanelCategoryMap.RollIn
: activeDockCategory === DockPanelCategories.Floating
? DockPanelCategoryMap.Floating
: activeDockCategory === DockPanelCategories.Sectional
? DockPanelCategoryMap.Sectional
: null;
const dockObj = docks
.map((dock) => {
if (dock.category === category && dock.materials === activeMaterial) {
return dock;
}
})
.filter(Boolean);
// console.log( dockObj )
setDock(() => [...dockObj]);
}
if (DockPanelCategories.Wedges === activeDockCategory) {
}
}, [activeMaterial, activeDockCategory, docks, wedgesAndRamps]);
useEffect(() => {
if (
[DockPanelCategories.BoatLift2, DockPanelCategories.BoatLift4].includes(
activeDockCategory
)
) {
// console.log( activeDockCategory, activeLiftRange )
const cylinder = CylinderType[activeDockCategory];
const boatliftsObj = boatlifts
.map((boatlift) => {
if (boatlift.no_of_cylinders === cylinder) {
return boatlift;
}
})
.filter(Boolean);
// console.log( boatliftsObj )
setBoatLift(() => [...boatliftsObj]);
}
}, [activeLiftRange, boatlifts, activeDockCategory]);
// useEffect(() => {
// (async () => {
// await sdk.projectHealth();
// })();
// }, []);
return (
<div className={`flex flex-col gap-x-[8px] border-t-2 w-full`}>
<div className={`w-full flex h-[54px] bg-gray-200`}>
<div
onClick={() => onMaterialClick(MaterialType.Gray)}
className={`grow h-full px-[12px] py-[5px] flex justify-center items-center cursor-pointer
${
activeMaterial === MaterialType.Gray
? " bg-white border-transparent"
: "bg-gray-200"
}`}
>
<img className={`rounded-md`} src={GrayMaterial} alt="GreyMaterial" />
</div>
<div
onClick={() => onMaterialClick(MaterialType.Perforated)}
className={`grow h-full px-[12px] py-[5px] flex justify-center items-center cursor-pointer
${
activeMaterial === MaterialType.Perforated
? " bg-white border-transparent"
: "bg-gray-200"
}`}
>
<img
className={`rounded-md`}
src={PerforatedMaterial}
alt="PerforatedMaterial"
/>
</div>
<div
onClick={() => onMaterialClick(MaterialType.Woodgrain)}
className={`grow h-full px-[12px] py-[5px] flex justify-center items-center cursor-pointer
${
activeMaterial === MaterialType.Woodgrain
? " bg-white border-transparent"
: "bg-gray-200"
}`}
>
<img
className={`rounded-md`}
src={WoodgrainMaterial}
alt="WoodgrainMaterial"
/>
</div>
</div>
<div className={`h-fit `}>
<div className={`text-black w-full h-fit overflow-y-auto`}>
<div
onClick={() => onDockPanelClick(DockPanelCategories.RollIn)}
className={`w-full h-[67px] z-30 flex items-center pl-3 gap-x-1 font-bold text-[18px] leading-[150%] tracking-tight text-[#111322] cursor-pointer`}
>
<Chevron
active={
activeDockCategory === DockPanelCategories.RollIn ? true : false
}
/>
{DockPanelCategories.RollIn}
</div>
<div
className={`${
activeDockCategory === DockPanelCategories.RollIn
? "block"
: "close-dock-panel hidden"
} px-[12px]`}
>
{dock.length ? (
<>
<img src={dock[0].image} alt="" className={`rounded-md my-2`} />
<div className={`grid grid-cols-2`}>
{dock?.map((dockItem, index) => (
<button
key={index}
onClick={() => onDockSelect(dockItem)}
className={`w-[100px] h-[47px] rounded py-2 mx-1 mt-1 border border-[#B9C0D4]`}
>
<div
className={`flex items-center justify-center gap-x-1`}
>
<span>{dockItem.width}'</span>
<span>x</span>
<span>{dockItem.length}'</span>
</div>
</button>
))}
</div>
</>
) : null}
</div>
<div
onClick={() => onDockPanelClick(DockPanelCategories.Floating)}
className={`w-full h-[67px] z-30 border-b-2 border-b-[#B9C0D4] flex items-center pl-3 gap-x-1 font-bold text-[18px] leading-[150%] tracking-tight text-[#111322] cursor-pointer`}
>
<Chevron
active={
activeDockCategory === DockPanelCategories.Floating
? true
: false
}
/>
{DockPanelCategories.Floating}
</div>
<div
className={`${
activeDockCategory === DockPanelCategories.Floating
? "block"
: "close-dock-panel hidden"
} px-[12px]`}
>
{dock.length ? (
<>
<img src={dock[0].image} alt="" className={`rounded-md my-2`} />
<div className={`grid grid-cols-2`}>
{dock?.map((dockItem, index) => (
<button
key={index}
onClick={() => onDockSelect(dockItem)}
className={`w-[100px] h-[47px] rounded py-2 mx-1 mt-1 border border-[#B9C0D4]`}
>
<div
className={`flex items-center justify-center gap-x-1`}
>
<span>{dockItem.width}'</span>
<span>x</span>
<span>{dockItem.length}'</span>
</div>
</button>
))}
</div>
</>
) : null}
</div>
<div
onClick={() => onDockPanelClick(DockPanelCategories.Sectional)}
className={`w-full h-[67px] z-30 border-b-2 border-b-[#B9C0D4] flex items-center pl-3 gap-x-1 font-bold text-[18px] leading-[150%] tracking-tight text-[#111322] cursor-pointer`}
>
<Chevron
active={
activeDockCategory === DockPanelCategories.Sectional
? true
: false
}
/>
{DockPanelCategories.Sectional}
</div>
<div
className={`${
activeDockCategory === DockPanelCategories.Sectional
? "block"
: "close-dock-panel hidden"
} px-[12px]`}
>
{dock.length ? (
<>
<img src={dock[0].image} alt="" className={`rounded-md my-2`} />
<div className={`grid grid-cols-2`}>
{dock?.map((dockItem, index) => (
<button
key={index}
onClick={() => onDockSelect(dockItem)}
className={`w-[100px] h-[47px] rounded py-2 mx-1 mt-1 border border-[#B9C0D4]`}
>
<div
className={`flex items-center justify-center gap-x-1`}
>
<span>{dockItem.width}'</span>
<span>x</span>
<span>{dockItem.length}'</span>
</div>
</button>
))}
</div>
</>
) : null}
</div>
<div
onClick={() => onDockPanelClick(DockPanelCategories.Wedges)}
className={`w-full h-[67px] z-30 border-b-2 border-b-[#B9C0D4] flex items-center pl-3 gap-x-1 font-bold text-[18px] leading-[150%] tracking-tight text-[#111322] cursor-pointer`}
>
<Chevron
active={
activeDockCategory === DockPanelCategories.Wedges ? true : false
}
/>
{DockPanelCategories.Wedges}
</div>
<div
className={`${
activeDockCategory === DockPanelCategories.Wedges
? "block"
: "close-dock-panel hidden"
} px-[12px]`}
>
{wedgesAndRamps?.wedges.length ? (
<>
<img
src={wedgesAndRamps?.wedges[0].image}
alt=""
className={`rounded-md my-2`}
/>
<div className={`grid grid-cols-2`}>
{wedgesAndRamps?.wedges?.map((dockItem, index) => (
<button
key={index}
onClick={() => onDockSelect(dockItem)}
className={`w-[100px] flex flex-col items-center flex-1 h-[47px] rounded py-1 mx-1 mt-1 border border-[#B9C0D4]`}
>
<div
className={`flex items-center justify-center gap-x-1`}
>
<span>{dockItem.width}'</span>
<span>x</span>
<span>{dockItem.length}'</span>
</div>
<span className={`text-xs`}>
{getWedgesCategory(dockItem.category)}
</span>
</button>
))}
</div>
</>
) : null}
</div>
<div
onClick={() => onDockPanelClick(DockPanelCategories.Ramps)}
className={`w-full h-[67px] z-30 border-b-2 border-b-[#B9C0D4] flex items-center pl-3 gap-x-1 font-bold text-[18px] leading-[150%] tracking-tight text-[#111322] cursor-pointer`}
>
<Chevron
active={
activeDockCategory === DockPanelCategories.Ramps ? true : false
}
/>
{DockPanelCategories.Ramps}
</div>
<div
className={`${
activeDockCategory === DockPanelCategories.Ramps
? "block"
: "close-dock-panel hidden"
} px-[12px]`}
>
{wedgesAndRamps.ramps.length ? (
<>
<img
src={wedgesAndRamps.ramps[0].image}
alt=""
className={`rounded-md my-2`}
/>
<div className={`grid grid-cols-2`}>
{wedgesAndRamps.ramps?.map((dockItem, index) => (
<button
key={index}
onClick={() => onDockSelect(dockItem)}
className={`w-[100px] flex flex-col items-center flex-1 h-[47px] rounded py-1 mx-1 mt-1 border border-[#B9C0D4]`}
>
<div
className={`flex items-center justify-center gap-x-1`}
>
<span>{dockItem.width}'</span>
<span>x</span>
<span>{dockItem.length}'</span>
</div>
<span className={`text-xs`}>
{getRampsCategory(dockItem?.category)}
</span>
</button>
))}
</div>
</>
) : null}
</div>
<div
onClick={() => onDockPanelClick(DockPanelCategories.BoatLift2)}
className={`w-full h-[67px] z-30 border-b-2 border-b-[#B9C0D4] flex items-center pl-3 gap-x-1 font-bold text-[18px] leading-[150%] tracking-tight text-[#111322] cursor-pointer`}
>
<Chevron
active={
activeDockCategory === DockPanelCategories.BoatLift2
? true
: false
}
/>
{DockPanelCategories.BoatLift2}
</div>
<div
className={`${
activeDockCategory === DockPanelCategories.BoatLift2
? "block"
: "close-dock-panel hidden"
} px-[12px]`}
>
{boatLift.length ? (
<>
<div
className={`flex flex-col w-full justify-center items-center `}
>
<div
className={`w-full h-5 text-center text-[#4A5578] font-normal text-[14px] leading-[20px]`}
>
Lift Range
</div>
<div className={`flex w-full justify-between items-center`}>
{liftRanges.map((liftRange) => (
<button
onClick={() => setActiveLiftRange(liftRange)}
className={`grow h-[43px] text-[#4A5578] font-normal text-[18px] leading-[150%] tracking-wide bg-white p-2 ${
activeLiftRange === liftRange
? "border-b-2 border-b-[#0F75BC]"
: ""
} `}
key={liftRange}
>
{liftRange}ft
</button>
))}
</div>
</div>
<img
src={boatLift[0].image}
alt=""
className={`rounded-md my-2`}
/>
<div className={`grid grid-cols-2`}>
{boatLift?.map((dockItem, index) => {
if (dockItem?.lift_range === activeLiftRange) {
return (
<div key={index}>
<button
onClick={() => onDockSelect(dockItem)}
className={`w-[100px] h-[47px] rounded py-2 mx-1 mt-1 border border-[#B9C0D4] `}
>
{dockItem.weight_capacity}
</button>
</div>
);
}
})}
</div>
</>
) : null}
</div>
<div
onClick={() => onDockPanelClick(DockPanelCategories.BoatLift4)}
className={`w-full h-[67px] z-30 border-b-2 border-b-[#B9C0D4] flex items-center pl-3 gap-x-1 font-bold text-[18px] leading-[150%] tracking-tight text-[#111322] cursor-pointer`}
>
<Chevron
active={
activeDockCategory === DockPanelCategories.BoatLift4
? true
: false
}
/>
{DockPanelCategories.BoatLift4}
</div>
<div
className={`${
activeDockCategory === DockPanelCategories.BoatLift4
? "block"
: "close-dock-panel hidden"
} px-[12px]`}
>
{boatLift.length ? (
<>
<div
className={`flex flex-col w-full justify-center items-center `}
>
<div
className={`w-full h-5 text-center text-[#4A5578] font-normal text-[14px] leading-[20px]`}
>
Lift Range
</div>
<div className={`flex w-full justify-between items-center`}>
{liftRanges.map((liftRange) => (
<button
onClick={() => setActiveLiftRange(liftRange)}
className={`grow h-[43px] text-[#4A5578] font-normal text-[18px] leading-[150%] tracking-wide bg-white p-2 ${
activeLiftRange === liftRange
? "border-b-2 border-b-[#0F75BC]"
: ""
}`}
key={liftRange}
>
{liftRange}ft
</button>
))}
</div>
</div>
<img
src={boatLift[0].image}
alt=""
className={`rounded-md my-2`}
/>
<div className={`grid grid-cols-2`}>
{boatLift?.map((dockItem, index) => {
if (dockItem?.lift_range === activeLiftRange) {
return (
<div key={index}>
<button
onClick={() => onDockSelect(dockItem)}
className={`w-[100px] h-[47px] rounded py-2 mx-1 mt-1 border border-[#B9C0D4] `}
>
{dockItem.weight_capacity}
</button>
</div>
);
}
})}
</div>
</>
) : null}
</div>
<div
onClick={() => onDockPanelClick(DockPanelCategories.Accessories)}
className={`w-full h-[67px] z-30 border-b-2 border-b-[#B9C0D4] flex items-center pl-3 gap-x-1 font-bold text-[18px] leading-[150%] tracking-tight text-[#111322] cursor-pointer`}
>
<Chevron
active={
activeDockCategory === DockPanelCategories.Accessories
? true
: false
}
/>
{DockPanelCategories.Accessories}
</div>
<div
className={`${
activeDockCategory === DockPanelCategories.Accessories
? "block"
: "close-dock-panel hidden"
} px-[12px]`}
>
{accessories.length ? (
<>
<div className={`grid grid-cols-2`}>
{accessories?.map((dockItem, index) => (
<div
key={index}
onClick={() => onDockSelect(dockItem)}
className={`h-fit rounded mx-1 mt-1 border border-[#B9C0D4] cursor-pointer`}
>
{/* <div className={ `flex items-center justify-center gap-x-1` }>
<span>{ dockItem.width }'</span>
<span>x</span>
<span>{ dockItem.length }'</span> */}
<img
src={dockItem.thumbnail}
alt=""
className={`rounded-md `}
/>
{/* </div> */}
</div>
))}
</div>
</>
) : null}
</div>
</div>
</div>
</div>
);
};
{
/* <div className={ `grow flex flex-col justify-center items-start bg-transparent` }>
</div> */
}
+1
View File
@@ -0,0 +1 @@
export { Builder } from './Builder'
+80
View File
@@ -0,0 +1,80 @@
import React, { memo } from 'react';
import { ArrowLeftIcon, ArrowRrightIcon, } from 'Assets/svgs';
import { EstimateSteps, } from 'Utils/constants';
const Comments = ( { step, onStepChange, onUpdateComment, onSubmit, errorMessage, submitLoading, comments } ) => {
{/* Comments */ }
return (
<div className={ `${ step === EstimateSteps.Comments ? '' : 'hidden' }` }>
<div>
<button
disabled={ submitLoading }
className={ `text-[#4A5578] leading-[150%] tracking-tight text-[14px] font-medium flex items-center` }
onClick={ () => onStepChange( EstimateSteps.LakeSurrounding ) }
>
<ArrowLeftIcon /> Back
</button>
<div className={ `flex items-center gap-x-2` }>
<p className={ ` uppercase text-[#0F75BC] leading-[150%] tracking-wide text-[18px] font-bold` }>Comments</p>
<span className={ `text-[#4A5578] leading-[150%] tracking-tight text-[14px] font-medium` }>(3 of 3)</span>
</div>
</div>
<div>
<fieldset className="cus-input">
<label
htmlFor="comments"
className="text-[#4A5578] leading-[20px] tracking-tight text-[14px] font-medium"
>
<span className={ `text-red-600 mr-2` }>*</span>Comments
</label>
<div className="relative mb-6">
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
{/* <img
className="w-5 h-5 object-contain"
src={ `` }
/> */}
</div>
<textarea
name="comments" cols="30" rows="10"
type="text"
id="comments"
disabled={ submitLoading }
className="bg-gray-50 border-2 border-[#D1D5DB] text-black text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-3 dark:bg-gray-200 dark:border-gray-600 dark:placeholder-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500 outline-none"
placeholder="Comments"
onKeyUp={ ( e ) => onUpdateComment( e.target.value ) }
>
</textarea>
</div>
</fieldset>
</div>
<div className={ `flex w-full justify-center bg-transparent ` }>
<button
disabled={ submitLoading || !comments }
className={ `h-[44px] w-full rounded border-2 border-[#0F75BC] flex justify-center gap-x-2 items-center bg-[#0F75BC] text-[#ffffff] uppercase font-medium tracking-wide leading-[24px] text-[16px] ${ submitLoading || !comments ? "cursor-not-allowed" : "" }` }
onClick={ onSubmit }
>
{ submitLoading ?
<>
<svg className="animate-spin -ml-1 mr-3 h-5 w-5 text-white inline" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg> <>Submit Estimate Inquiry <ArrowRrightIcon /></>
</>
: <>Submit Estimate Inquiry <ArrowRrightIcon /></>
}
</button>
</div>
<p className="text-red-500 text-xs italic capitalize">{ errorMessage }</p>
</div>
);
}
const CommentsMemo = memo( Comments );
export { CommentsMemo as Comments };
+1
View File
@@ -0,0 +1 @@
export { Comments } from './Comments'
@@ -0,0 +1,324 @@
import React, { memo, useState, useCallback, useEffect } from 'react';
import { ArrowRrightIcon } from 'Assets/svgs';
import { EstimateSteps, Tables, Truthy } from 'Utils/constants';
import MkdSDK from 'Utils/MkdSDK';
const sdk = new MkdSDK()
const ContactInformation = ( {
step,
onStepChange,
hasDealer,
onHasDealerChange,
onDealerChange,
setFirstName,
setLastName,
setEmail,
setPhone,
setLake,
setCity,
setCountry,
} ) => {
const [ TC, setTC ] = useState( false )
const [ dealers, setDealers ] = useState( [] )
const getDealers = useCallback( ( table ) => {
( async () => {
try {
const result = await sdk.getItems( table )
setDealers( result?.model )
} catch ( error ) {
console.log( error.message )
}
} )()
}, [ dealers ] )
useEffect( () => {
if ( !dealers.length ) {
getDealers( Tables.Dealers )
}
}, [ dealers ] )
{/* ContactInformation */ }
return (
<div className={ `h-[500px] max-h-[500px] overflow-auto ${ step === EstimateSteps.ContactInformation ? '' : 'hidden' }` }>
<div>
<div className={ `flex items-center gap-x-2` }>
<p className={ ` uppercase text-[#0F75BC] leading-[150%] tracking-wide text-[18px] font-bold` }>Contact information</p>
<span className={ `text-[#4A5578] leading-[150%] tracking-tight text-[14px] font-medium` }>(1 of 3)</span>
</div>
</div>
<div className={ `` }>
<div className={ `flex w-full px-0 gap-x-2 justify-between items-center ` }>
<fieldset className="cus-input w-1/2">
<label
htmlFor="firstName"
className="text-[#4A5578] leading-[20px] tracking-tight text-[14px] font-medium"
>
<span className={ `text-red-600 mr-2` }>*</span>First Name
</label>
<div className="relative mb-6">
{/* <div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
<img
className="w-5 h-5 object-contain"
src={ `` }
/>
</div> */}
<input
type="text"
id="firstName"
className="bg-gray-50 border-2 border-[#D1D5DB] text-black text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full pl-3 p-3 dark:bg-gray-200 dark:border-gray-600 dark:placeholder-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500 outline-none"
placeholder="John"
onChange={ ( e ) => setFirstName( e.target.value ) }
/>
</div>
</fieldset>
<fieldset className="cus-input w-1/2">
<label
htmlFor="lastName"
className="text-[#4A5578] leading-[20px] tracking-tight text-[14px] font-medium"
>
<span className={ `text-red-600 mr-2` }>*</span>Last Name
</label>
<div className="relative mb-6">
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
{/* <img
className="w-5 h-5 object-contain"
src={ `` }
/> */}
</div>
<input
type="text"
id="lastName"
className="bg-gray-50 border-2 border-[#D1D5DB] text-black text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full pl-3 p-3 dark:bg-gray-200 dark:border-gray-600 dark:placeholder-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500 outline-none"
placeholder="Doe"
onChange={ ( e ) => setLastName( e.target.value ) }
/>
</div>
</fieldset>
</div>
<fieldset className="cus-input">
<label
htmlFor="email"
className="text-[#4A5578] leading-[20px] tracking-tight text-[14px] font-medium"
>
<span className={ `text-red-600 mr-2` }>*</span>Email
</label>
<div className="relative mb-6">
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
{/* <img
className="w-5 h-5 object-contain"
src={ `` }
/> */}
</div>
<input
type="email"
id="email"
className="bg-gray-50 border-2 border-[#D1D5DB] text-black text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full pl-3 p-3 dark:bg-gray-200 dark:border-gray-600 dark:placeholder-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500 outline-none"
placeholder="JohnDoe@example.com"
onChange={ ( e ) => setEmail( e.target.value ) }
/>
</div>
</fieldset>
<fieldset className="cus-input">
<label
htmlFor="phoneNumber"
className="text-[#4A5578] leading-[20px] tracking-tight text-[14px] font-medium"
>
<span className={ `text-red-600 mr-2` }>*</span>Telephone
</label>
<div className="relative mb-6">
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
{/* <img
className="w-5 h-5 object-contain"
src={ `` }
/> */}
</div>
<input
type="tel"
id="phoneNumber"
className="bg-gray-50 border-2 border-[#D1D5DB] text-black text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full pl-3 p-3 dark:bg-gray-200 dark:border-gray-600 dark:placeholder-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500 outline-none"
// placeholder="James"
onChange={ ( e ) => setPhone( e.target.value ) }
/>
</div>
</fieldset>
<div className={ `flex w-full px-0 gap-x-2 justify-between items-center ` }>
<fieldset className="cus-input w-1/3">
<label
htmlFor="lake"
className="text-[#4A5578] leading-[20px] tracking-tight text-[14px] font-medium"
>
<span className={ `text-red-600 mr-2` }>*</span>Location
</label>
<div className="relative mb-6">
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
{/* <img
className="w-5 h-5 object-contain"
src={ `` }
/> */}
</div>
<input
type="text"
id="lake"
className="bg-gray-50 border-2 border-[#D1D5DB] text-black text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full pl-3 p-3 dark:bg-gray-200 dark:border-gray-600 dark:placeholder-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500 outline-none"
placeholder="Lake"
onChange={ ( e ) => setLake( e.target.value ) }
/>
</div>
</fieldset>
<fieldset className="cus-input w-1/3">
<label
htmlFor="city"
className="text-[#4A5578] leading-[20px] tracking-tight text-[14px] font-medium"
>
<span className={ `text-red-600 mr-2` }>*</span>City
</label>
<div className="relative mb-6">
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
{/* <img
className="w-5 h-5 object-contain"
src={ `` }
/> */}
</div>
<input
type="text"
id="city"
className="bg-gray-50 border-2 border-[#D1D5DB] text-black text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full pl-3 p-3 dark:bg-gray-200 dark:border-gray-600 dark:placeholder-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500 outline-none"
placeholder="City"
onChange={ ( e ) => setCity( e.target.value ) }
/>
</div>
</fieldset>
<fieldset className="cus-input w-1/3">
<label
htmlFor="country"
className="text-[#4A5578] leading-[20px] tracking-tight text-[14px] font-medium"
>
<span className={ `text-red-600 mr-2` }>*</span>Country
</label>
<div className="relative mb-6">
{/* <div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
<img
className="w-5 h-5 object-contain"
src={ `` }
/>
</div> */}
<select
type="text"
id="country"
className="bg-gray-50 border-2 border-[#D1D5DB] text-black text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full pl-3 p-3 dark:bg-gray-200 dark:border-gray-600 dark:placeholder-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500 outline-none"
placeholder="Country"
onChange={ ( e ) => setCountry( e.target.value ) }
>
<option></option>
<option value={ `Canada` }>Canada</option>
</select>
</div>
</fieldset>
</div>
<fieldset className="cus-input">
<label
// htmlFor="city"
className="text-[#4A5578] leading-[20px] tracking-tight text-[14px] font-medium"
>
<span className={ `text-red-600 mr-2` }>*</span>Are you currently working with a Dealer in your area?
</label>
<div className="relative flex items-center justify-start mb-6">
<input
type="radio"
name="dealer"
id="yesDealer"
className="bg-gray-50 border-2 border-[#D1D5DB] text-black text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-[44px] pl-3 p-3 dark:bg-gray-200 dark:border-gray-600 dark:placeholder-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500 outline-none"
onChange={ () => onHasDealerChange( Truthy.True ) }
/>
<label
htmlFor="yesDealer"
className="text-[#4A5578] leading-[20px] tracking-tight text-[14px] font-medium"
>Yes
</label>
<input
type="radio"
name="dealer"
id="noDealer"
className="bg-gray-50 border-2 border-[#D1D5DB] text-black text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-[44px] pl-3 p-3 dark:bg-gray-200 dark:border-gray-600 dark:placeholder-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500 outline-none"
onChange={ () => onHasDealerChange( Truthy.False ) }
/>
<label
htmlFor="noDealer"
className="text-[#4A5578] leading-[20px] tracking-tight text-[14px] font-medium"
>No
</label>
</div>
</fieldset>
{ hasDealer ?
<fieldset className="cus-input">
<label
htmlFor="dealers"
className="text-[#4A5578] leading-[20px] tracking-tight text-[14px] font-medium"
>
<span className={ `text-red-600 mr-2` }>*</span>Dealers
</label>
<div className="relative mb-6">
{/* <div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
<img
className="w-5 h-5 object-contain"
src={ `` }
/>
</div> */}
<select
id="dealers"
className="bg-gray-50 border-2 border-[#D1D5DB] text-black text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full pl-3 p-3 dark:bg-gray-200 dark:border-gray-600 dark:placeholder-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500 outline-none"
placeholder="Delaers"
onChange={ ( e ) => onDealerChange( e.target.value ) }
>
<>
<option></option>
{ dealers?.length ? dealers.map( ( dealer ) => (
<option key={ dealer?.id } value={ dealer?.id }>{ dealer?.name }</option>
) )
: null }
</>
</select>
</div>
</fieldset>
: null
}
<fieldset className="cus-input">
<div className="relative flex items-center justify-start mb-6">
<input
// ref={tcRef}
type="checkbox"
name="desclaimer"
id="desclaimer"
checked={ TC }
className="bg-gray-50 border-2 border-[#D1D5DB] text-black text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-[44px] pl-0 p-3 dark:bg-gray-200 dark:border-gray-600 dark:placeholder-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500 outline-none self-start mt-1"
onChange={ ( e ) => setTC( e.target.checked ) }
/>
<label
htmlFor="desclaimer"
className="text-[#4A5578] leading-[20px] tracking-tight text-[14px] font-medium"
>Terms And Conditions
</label>
</div>
</fieldset>
</div>
<div className={ `flex w-full justify-center bg-transparent ` }>
<button
disabled={ !TC }
className={ `h-[44px] w-full rounded border-2 border-[#0F75BC] flex justify-center gap-x-2 items-center bg-[#0F75BC] text-[#ffffff] uppercase font-medium tracking-wide leading-[24px] text-[16px]` }
onClick={ () => onStepChange( EstimateSteps.LakeSurrounding ) }
>
Continue <ArrowRrightIcon />
</button>
</div>
</div>
);
}
const ContactInformationMemo = memo( ContactInformation );
export { ContactInformationMemo as ContactInformation };
@@ -0,0 +1 @@
export { ContactInformation } from './ContactInformation'
+823
View File
@@ -0,0 +1,823 @@
import React, {
useEffect,
useMemo,
useCallback,
useRef,
useState,
useContext
} from "react";
import { FabricJSCanvas, useFabricJSEditor } from "fabricjs-react";
import { fabric } from "fabric";
import { DockSidebar } from "Components/DockSidebar";
import { ActionButtons } from "Components/ActionButtons";
import MkdSDK from "Utils/MkdSDK";
import { Stack } from "Utils/utils";
import { EstimateModal } from "Components/EstimateModal";
import * as XLSX from "xlsx";
import { BuildCanvasFromLocalModal } from "Components/BuildCanvasFromLocalModal";
import { GlobalContext } from "../../globalContext";
import { clearClone, clone } from "Utils/DockBuilderUtils/clone";
import {
edgeDetection,
handleEdgeDetection,
handleIntersection
} from "Utils/DockBuilderUtils";
import {
CanvasModes,
deleteIcon,
DockPanelCategories,
edgeSnapThreshold,
nintyDeg,
oneEightyDeg,
oneFeet,
rotateIcon,
scaleFactor,
twoSeventyDeg
} from "Utils/constants";
import {
reScaleXY,
resolveHeight,
resolveWidth
} from "Utils/DockBuilderUtils/edgeDetection";
import { DeleteIcon, RotateIcon } from "Assets/svgs";
import { capitalize } from "Utils/helper";
const sdk = new MkdSDK();
const stack = new Stack();
export const DockBuilder = () => {
let clipboard = null;
let mouseDownPoint = null;
let shiftKeyDown = false;
window.counter = 0;
let prevPointer = null;
let isDragging = false;
let isZoomed = false;
let startX, startY;
const { dispatch } = useContext(GlobalContext);
const { editor, onReady } = useFabricJSEditor({ selection: true });
const canvasModeRef = useRef(null);
const fileRef = useRef();
const imageRef = useRef();
const editorMemo = useMemo(() => editor?.canvas, [editor?.canvas]);
// const { dispatch } = React.useContext( AuthContext );
const [objHovered, setObjHovered] = useState(false);
const [selectedItems, setSelectedItems] = useState([]);
const [estimateLoading, setEstimateLoading] = useState(false);
const [showEstimateModal, setShowEstimateModal] = useState(false);
const [showBuildCanvasFromLocalModal, setShowBuildCanvasFromLocalModal] =
useState(false);
const [initialLoad, setInitialLoad] = useState(true);
const [linesAdded, setLinesAdded] = useState(false);
const [dockImage, setDockImage] = useState(null);
const [editorReady, setEditorReady] = useState(false);
const [dockLabel, setDockLabel] = useState(null);
const [dockDimensions, setDockDimensions] = useState(null);
const deleteImg = document.createElement("img");
deleteImg.src = deleteIcon;
const rotateImg = document.createElement("img");
rotateImg.src = rotateIcon;
const onEstimateModalClose = useCallback(() => {
setShowEstimateModal(false);
}, [showEstimateModal]);
const onEstimateModalOpen = useCallback(() => {
const ext = "png";
const base64 = editorMemo.toDataURL({
format: ext,
enableRetinaScaling: true
});
setDockImage(base64);
setShowEstimateModal(true);
}, [showEstimateModal, editorMemo, dockImage]);
// const onAddRect = useCallback( () => {
// editor?.canvas?.add( rect );
// window.counter++;
// // newleft += 200;
// }, [ editor ] );
const toJSON = () => {
// TODO: download the json file
// TODO: get json of editor content
// TODO: Ensure dockData is included in the json
// TODO: save the json to the local storage as dock
// TODO: name the file as paradise_dock_<timestamp here>.dock
};
const uploadFile = (e) => {
// TODO: Our own upload the file we must have downloaded previously
// TODO: extract the json from the file
// TODO: load the json to the editor
// TODO: render all
};
const downloadImage = useCallback(() => {
// // console.log( 'Download' )
const ext = "png";
// TODO: download the image
// TODO: get the json of the editor content
// TODO: extract the dockData from the json
// TODO: filter the dockData to ensure it does not contain snapClone
// TODO: generate the base64 image
// TODO: download the image and name it as paradise_dock_snapshot_<timestamp here>.${ext}
// TODO: download the excel file of the dockData you extracted
// TODO: check if dockData is empty
// TODO: generate the base64 image
// TODO: trigger download of the dockData you extracted in excel format
}, [editorMemo]);
const renderBg = useCallback(() => {
const imageURL =
"https://s3.us-east-2.amazonaws.com/com.nds.images/download.png";
new fabric.Image.fromURL(
imageURL,
function (img) {
img.set({
scaleX: editorMemo.width / img.width,
scaleY: editorMemo.height / img.height
});
editorMemo.setBackgroundImage(img);
editorMemo.renderAll();
// updateModifications( true, )
},
{
crossOrigin: "anonymous"
}
);
}, [editorMemo]);
const downloadExcel = useCallback((data) => {
const worksheet = XLSX.utils.json_to_sheet(data);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, "Selected Items");
XLSX.writeFile(workbook, "Paradise_dock_selected_items.xlsx");
}, []);
function updateZoom(opt) {
let delta = opt.e.deltaY;
let pointer = editorMemo.getPointer(opt.e);
let zoom = editorMemo.getZoom();
if (delta > 0) {
zoom *= 1.1;
} else {
zoom /= 1.1;
}
if (zoom > 5) zoom = 5;
if (zoom < 1) zoom = 1;
// console.log( zoom )
// editorMemo.setBackgroundImageStretch=false
var vpt = editorMemo.viewportTransform;
if (zoom === 1) {
vpt[4] = 0;
vpt[5] = 0;
editorMemo.selection = true;
this.isZoomed = false;
}
if (zoom > 1) {
this.isZoomed = true;
}
fabric.util.animate({
startValue: editorMemo.getZoom(),
endValue: zoom,
// duration: 0,
onChange: function (value) {
editorMemo.zoomToPoint({ x: pointer.x, y: pointer.y }, value);
},
fps: 1080
});
opt.e.preventDefault();
opt.e.stopPropagation();
}
function handleZoomMouseDown(options) {
var pointer = editorMemo.getPointer(options.e, true);
mouseDownPoint = new fabric.Point(pointer.x, pointer.y);
if (options.target === null && this.isZoomed) {
editorMemo.upperCanvasEl.style.cursor = "grabbing";
editorMemo.selection = false;
// this.lastPosX = options.e.movementX;
// this.lastPosY = options.e.movementY;
this.lastPosX = options.e.clientX;
this.lastPosY = options.e.clientY;
//
}
// else if ( options.target !== null && this.isZoomed ) {
// var activeObject = editorMemo.getActiveObject();
// if ( activeObject ) {
// activeObject.lockMovementX = true;
// activeObject.lockMovementY = true;
// activeObject.lockScalingX = true;
// activeObject.lockScalingY = true;
// }
// }
}
const handleZoomMouseUp = useCallback(() => {
editorMemo.defaultCursor = "default";
mouseDownPoint = null;
editorMemo.selection = true;
}, [editor]);
function handleZoomMouseMove(options) {
if (options.target === null && mouseDownPoint && this.isZoomed) {
editorMemo.selection = false;
editorMemo.upperCanvasEl.style.cursor = "grabbing";
var delta = new fabric.Point(options.e.movementX, options.e.movementY);
editorMemo.relativePan(delta);
editorMemo.viewportTransform[4] = Math.max(
Math.min(editorMemo.viewportTransform[4], 0),
editorMemo.getWidth() - editorMemo.getWidth() * editorMemo.getZoom()
);
editorMemo.viewportTransform[5] = Math.max(
Math.min(editorMemo.viewportTransform[5], 0),
editorMemo.getHeight() - editorMemo.getHeight() * editorMemo.getZoom()
);
// var vpt = this.viewportTransform;
// vpt[ 4 ] += options.e.clientX - this.lastPosX;
// vpt[ 5 ] += options.e.clientY - this.lastPosY;
requestAnimationFrame(function () {
editorMemo.renderAll();
this.requestRenderAll();
});
// this.lastPosX = options.e.movementX;
// this.lastPosY = options.e.movementY;
// this.lastPosX = options.e.clientX;
// this.lastPosY = options.e.clientY;
}
}
const addEdgeDetectionClone = useCallback(
(options) => {
objectMoving = true;
if (options) {
const selectedObj = options.target.setCoords();
editorMemo.defaultCursor = "grabbing";
if (
selectedObj.dockData &&
selectedObj.dockData.itemName === DockPanelCategories.Accessories
) {
return;
}
editorMemo.forEachObject((obj) => {
const pointer = { x: options.e.clientX, y: options.e.clientY };
if (obj === selectedObj) return;
// canvas.setCursor('grabbing');
// // console.log(pointer.x, pointer.y);
if (!obj.snapClone && obj.type === "image") {
// // console.log( pointer, prevPointer )
if (
prevPointer &&
(prevPointer.x !== pointer.x || prevPointer.y !== pointer.y)
) {
switch (true) {
case edgeDetection(selectedObj, obj, "left"):
clone(selectedObj, editorMemo, obj, "left");
break;
case edgeDetection(selectedObj, obj, "right"):
clone(selectedObj, editorMemo, obj, "right");
break;
case edgeDetection(selectedObj, obj, "top"):
clone(selectedObj, editorMemo, obj, "top");
break;
case edgeDetection(selectedObj, obj, "bottom"):
clone(selectedObj, editorMemo, obj, "bottom");
break;
case edgeDetection(selectedObj, obj, "neutral"):
clearClone(editorMemo);
break;
default:
// console.log( "edge detection switch default" )
}
// prevPointer = null;
}
prevPointer = pointer;
// // console.log( pointer, prevPointer )
}
// if ( edgeDetection( selectedObj, obj, "left" ) ) {
// clone( selectedObj, editorMemo, obj, "left" )
// } else {
// // clearClone( editorMemo )
// }
// if ( edgeDetection( selectedObj, obj, 'right' ) ) {
// clone( selectedObj, editorMemo, obj, "right" )
// } else {
// // clearClone( editorMemo )
// }
// if ( edgeDetection( selectedObj, obj, 'top' ) ) {
// clone( selectedObj, editorMemo, obj, "top" )
// } else {
// // clearClone( editorMemo )
// }
// if ( edgeDetection( selectedObj, obj, 'bottom' ) ) {
// clone( selectedObj, editorMemo, obj, "bottom" )
// } else {
// // clearClone( editorMemo )
// }
});
}
},
[editor, editorMemo]
);
const horizontalIndicatorLines = (newTop, objTop) => {
editorMemo.getObjects("line").forEach((obj) => {
if (obj.testLine) {
editorMemo.remove(obj);
}
});
let line = new fabric.Line([0, newTop, editorMemo.getWidth(), newTop], {
stroke: "#AAAAAA",
testLine: true
// strokeDashArray: [ 5 ],
});
let line2 = new fabric.Line([0, objTop, editorMemo.getWidth(), objTop], {
stroke: "#AAAAAA",
testLine: true
// strokeDashArray: [ 5 ],
});
editorMemo.add(line);
editorMemo.add(line2);
editorMemo.renderAll();
};
const verticleIndicatorLines = (newLeft, objLeft) => {
editorMemo.getObjects("line").forEach((obj) => {
if (obj.testLine) {
editorMemo.remove(obj);
}
});
let line = new fabric.Line([newLeft, 0, newLeft, editorMemo.getHeight()], {
stroke: "#AAAAAA",
testLine: true
// strokeDashArray: [ 5 ],
});
let line2 = new fabric.Line([objLeft, 0, objLeft, editorMemo.getWidth()], {
stroke: "#AAAAAA",
testLine: true
// strokeDashArray: [ 5 ],
});
editorMemo.add(line);
editorMemo.add(line2);
editorMemo.renderAll();
};
function edgeDetectionAndSnap(options) {
if (this.isZoomed) {
return;
}
// TODO: Edge detection and snap to object within snap range
// TODO: Detect if the object is within the snap range of the selected object
// TODO: detect if the selected object contains dockData and dockData contains itemName of DockPanelCategories.Accessories || DockPanelCategories.BoatLift2 || DockPanelCategories.BoatLift4
// TODO: handle rotation of 0, 90, 180, 270 degrees
editorMemo.defaultCursor = "default";
prevPointer = null;
clearClone(editorMemo);
}
const showHighlight = useCallback(
(event) => {
if (event.target) {
if (event.target.type === "image") {
setObjHovered(true);
// console.log(event.target.dockData)
if (
event.target &&
event.target.dockData &&
event.target.dockData.image
) {
imageRef.current.src = event.target.dockData.image;
setDockLabel(
`${
event.target.dockData.materials
? capitalize(event.target.dockData.itemName)
: event.target.dockData.itemName
} (${
event.target.dockData.materials
? event.target.dockData.materials
: event.target.dockData.model
})`
);
setDockDimensions(
`${event.target.dockData.width}' x ${event.target.dockData.length}'`
);
} else if (
event.target &&
event.target.dockData &&
event.target.dockData.thumbnail
) {
setDockLabel(
`${capitalize(event.target.dockData.itemName)} (${
event.target.dockData.name
})`
);
setDockDimensions(
`${event.target.dockData.width}' x ${event.target.dockData.length}'`
);
imageRef.current.src = event.target.dockData.thumbnail;
}
}
}
if (!event.target && objHovered) {
setObjHovered(false);
setDockLabel(null);
setDockDimensions(null);
setDockDimensions(null);
imageRef.current.src = "";
}
},
[objHovered]
);
const hideHighlight = useCallback(() => {
if (objHovered) {
setObjHovered(false);
setDockLabel(null);
setDockDimensions(null);
imageRef.current.src = "";
}
}, [objHovered]);
const updateModifications = useCallback(
(savehistory, event) => {
if (savehistory === true) {
if ((event && event === "update") || event.target.dockData) {
const json = editor?.canvas?.toJSON(["dockData", "snapClone"]);
const data = JSON.stringify(json);
stack.updateStack(data);
updateLocalstore();
}
}
},
[selectedItems, editor]
);
const updateLocalstore = useCallback(() => {
const json = editor?.canvas?.toJSON(["dockData", "snapClone"]);
const newObjects = json.objects
.filter((object) => !object.snapClone)
.filter(Boolean);
json.objects = newObjects;
const items = json.objects
.map((object) => {
if (object.dockData && !object.snapClone) {
return object.dockData;
}
})
.filter(Boolean);
setSelectedItems(() => [...items]);
const data = JSON.stringify(json);
localStorage.setItem("dock", data);
}, [editor, selectedItems]);
const onUndoClick = useCallback(() => {
// TODO: Undo
}, [editorMemo]);
const onRedoClick = useCallback(() => {
// TODO: Redo
}, [editorMemo]);
const onPrintScreen = useCallback(() => {
// convert the canvas to a data url and download it.
// TODO: Print screen
// TODO: print screen without background image and background color, the background image should be white
// TODO: after printing, the background image and background color should be restored
}, [editorMemo]);
const CopySelection = () => {
// TODO: Copy selection
};
const PasteSelection = () => {
// TODO: Paste selection
};
const onDeleteSelection = () => {
// TODO: Delete selection
};
const addLines = useCallback(() => {
// Initiate a line instance
const editorHeight = editorMemo.getHeight();
const division = editorHeight / oneFeet;
if (!linesAdded) {
let initialFt = 8;
let multiplier = 1;
for (let i = division - 3; i > 0; i--) {
if (multiplier > division - 4) {
break;
}
let line = new fabric.Line(
[0, oneFeet * i, editorMemo.getWidth(), oneFeet * i],
{
stroke: "#AAAAAA",
strokeDashArray: [5]
}
);
let text = new fabric.Text(`${initialFt * multiplier}ft`, {
// stroke: '#000000',
fill: "#AAAAAA",
fontSize: 18,
left: editorMemo.getWidth() - 40,
top: oneFeet * i
});
multiplier++;
// Render the rectangle in canvas
editorMemo.add(line).sendToBack(line);
editorMemo.add(text).sendToBack(text);
editorMemo.renderAll();
}
setLinesAdded(true);
}
}, [editorMemo, linesAdded]);
const removeLines = useCallback(() => {
editorMemo.getObjects("line").forEach((line) => {
editorMemo.remove(line);
});
editorMemo.getObjects("text").forEach((text) => {
editorMemo.remove(text);
});
setLinesAdded(false);
}, [editorMemo, linesAdded]);
const onCloseBuildFromLocalModal = useCallback(() => {
setShowBuildCanvasFromLocalModal(false);
dispatch({
type: "DOCK_LOADING",
payload: false
});
setInitialLoad(false);
}, [editorMemo, showBuildCanvasFromLocalModal, dispatch, initialLoad]);
function renderIcon(icon) {
return function renderIcon(ctx, left, top, styleOverride, fabricObject) {
var size = this.cornerSize;
ctx.save();
ctx.translate(left, top);
ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle));
ctx.drawImage(icon, -size / 2, -size / 2, size, size);
ctx.restore();
};
}
function deleteObject(eventData, transform) {
var target = transform.target;
var canvas = target.canvas;
canvas.remove(target);
canvas.requestRenderAll();
}
useEffect(() => {
if (editor) {
editorMemo.backgroundColor = "#011e23";
editorMemo.enableRetinaScaling = false;
// Use hardware acceleration
// editorMemo.renderOnAddRemove = false;
editorMemo.skipOffscreen = true;
// Lower render quality setting
fabric.devicePixelRatio = 1;
fabric.Group.prototype.hasControls = true;
fabric.Group.prototype.snapAngle = 45;
fabric.Group.prototype.snapThreshold = 5;
fabric.Group.prototype.stroke = "#0f75bc";
fabric.Object.prototype.snapAngle = 45;
// Optimize object rendering
fabric.Object.prototype.objectCaching = true;
fabric.Object.prototype.snapThreshold = 5;
fabric.Object.prototype.setControlsVisibility({
tl: false, //top-left
mt: false, // middle-top
tr: true, //top-right
ml: false, //middle-left
mr: false, //middle-right
bl: false, // bottom-left
mb: false, //middle-bottom
br: false, //bottom-right
mtr: false // rotate icon
});
// fabric.Object.prototype.controls.deleteControl = new fabric.Control( {
// x: -0.8,
// y: -1,
// offsetY: 16,
// offsetX: 16,
// cursorStyle: 'pointer',
// mouseUpHandler: deleteObject,
// render: renderIcon( deleteImg ),
// cornerPadding: 0,
// cornerSize: 24
// } );
fabric.Object.prototype.controls.tr = new fabric.Control({
x: 0.5,
y: -0.5,
cursorStyle: "rotate",
offsetY: -16,
offsetX: 16,
cornerPadding: 0,
actionHandler: fabric.controlsUtils.rotationWithSnapping,
cursorStyleHandler: fabric.controlsUtils.rotationStyleHandler,
withConnection: true,
render: renderIcon(rotateImg),
cornerSize: 20,
actionName: "rotate",
sizeX: 40,
sizeY: 40,
touchSizeX: 40,
touchSizeY: 40
});
fabric.Object.prototype.hasControls = true;
fabric.Object.prototype.lockScalingX = true;
fabric.Object.prototype.lockScalingY = true;
fabric.Object.prototype.cornerSize = 5;
fabric.Line.prototype.hasBorders = false;
fabric.Line.prototype.cornerSize = 0;
fabric.Line.prototype.borderColor = "transparent";
fabric.Line.prototype.hasRotatingPoint = false;
fabric.Line.prototype.hasControls = false;
fabric.Line.prototype.lockRotation = true;
fabric.Line.prototype.hoverCursor = "default";
fabric.Line.prototype.selectable = false;
fabric.Line.prototype.lockMovementX = true;
fabric.Line.prototype.lockMovementY = true;
fabric.Text.prototype.hasBorders = false;
fabric.Text.prototype.cornerSize = 0;
fabric.Text.prototype.borderColor = "transparent";
fabric.Text.prototype.hasRotatingPoint = false;
fabric.Text.prototype.hoverCursor = "default";
fabric.Text.prototype.selectable = false;
fabric.Text.prototype.hasControls = false;
fabric.Text.prototype.lockRotation = true;
fabric.Text.prototype.lockMovementX = true;
fabric.Text.prototype.lockMovementY = true;
// fabric.Text.prototype.viewportCenter = true
setEditorReady(true);
editorMemo.setHeight(window.innerHeight);
editorMemo.setWidth(window.innerWidth);
fabric.Object.prototype.cornerColor = "#B9C0D4";
renderBg();
addLines();
// Intersection
// TODO: Edge detection and snap to object within snap range
editorMemo.on("object:moving", edgeDetectionAndSnap);
editorMemo.on("mouse:over", showHighlight);
editorMemo.on("mouse:out", hideHighlight);
editorMemo.on("object:modified", (e) => updateModifications(true, e));
editorMemo.on("object:added", (e) => updateModifications(true, e));
// Listen for the delete key event
document.onkeydown = (event) => {
if (event.keyCode === 46) {
// 46 is the keyCode for the delete key
onDeleteSelection();
}
};
const savedDock = localStorage.getItem("dock")
? JSON.parse(localStorage.getItem("dock"))
: null;
if (savedDock) {
if (initialLoad) {
setShowBuildCanvasFromLocalModal(true);
}
} else if (initialLoad) {
setInitialLoad(false);
dispatch({
type: "DOCK_LOADING",
payload: false
});
}
}
}, [editor]);
useEffect(() => {
if (editor) {
const handleResize = () => {
// let canvas = canvasRef.current;
editorMemo.setWidth(window.innerWidth);
editorMemo.setHeight(window.innerHeight);
editorMemo.setBackgroundImage(editorMemo.backgroundImage, (bgImage) => {
bgImage.set({
scaleX: editorMemo.width / bgImage.width,
scaleY: editorMemo.height / bgImage.height
});
});
removeLines();
addLines();
editorMemo.renderAll();
};
window.addEventListener("resize", handleResize);
handleResize();
return () => {
window.removeEventListener("resize", handleResize);
};
}
}, []);
return (
<div className={`relative`}>
<>
<DockSidebar
onDownloadImage={downloadImage}
onDownloadFile={toJSON}
onUploadFile={uploadFile}
fileRef={fileRef}
editor={editorMemo}
updateModifications={updateModifications}
selectedItems={selectedItems}
onEstimateModalOpen={onEstimateModalOpen}
/>
<FabricJSCanvas className={`floor-canvas`} onReady={onReady} />
<ActionButtons
className={`absolute w-fit top-4 right-8`}
onCopy={CopySelection}
onPaste={PasteSelection}
onRedoClick={onRedoClick}
onUndoClick={onUndoClick}
onPrintScreen={onPrintScreen}
onDeleteSelection={onDeleteSelection}
/>
<div
className={`rounded w-[10.875rem] absolute top-0 inset-x-0 m-auto ${
objHovered ? "" : "hidden"
}`}
>
{dockLabel ? (
<div
className={`w-full shadow text-white text-center font-medium text-[1.125rem] leading-[1.6875rem] tracking-[2%] font-['Rajdhani']`}
>
{dockLabel}
</div>
) : null}
<img
ref={imageRef}
src=""
alt=""
width={174}
height={116}
className={`rounded relative ${objHovered ? "" : "hidden"}`}
/>
{dockDimensions ? (
<div
className={`w-full shadow text-white text-center font-medium text-[1.125rem] leading-[1.6875rem] tracking-[2%] font-['Rajdhani']`}
>
{dockDimensions}
</div>
) : null}
</div>
</>
<EstimateModal
loading={estimateLoading}
modalCloseClick={onEstimateModalClose}
showEstimateModal={showEstimateModal}
dockImage={dockImage}
selectedItems={selectedItems}
/>
<BuildCanvasFromLocalModal
modalCloseClick={onCloseBuildFromLocalModal}
showBuildCanvasFromLocalModal={showBuildCanvasFromLocalModal}
editor={editorMemo}
/>
</div>
);
};
+1
View File
@@ -0,0 +1 @@
export { DockBuilder } from './DockBuilder'
@@ -0,0 +1,73 @@
import React, { useCallback, useContext } from 'react'
import { SidebarLogo } from 'Components/SidebarLogo'
import { GlobalContext } from '../../globalContext'
import { SidebarFoot } from 'Components/SidebarFoot'
import { SidebarBuilder } from 'Components/SidebarBuilder'
export const DockSidebar = ( { onAddItem, onDownloadImage, onDownloadFile, onUploadFile, fileRef, editor, updateModifications, selectedItems, onEstimateModalOpen } ) => {
const { state, dispatch } = useContext( GlobalContext )
const { dockSideBarOpen } = state
let toggleOpen = ( open ) =>
dispatch( {
type: "TOGGLE_DOCK_SIDEBAR",
payload: { dockSideBarOpen: open },
} );
const onWheel = useCallback( ( e ) => {
if ( e.ctrlKey ) {
return e.preventDefault();//prevent zoom
}
}, [] );
return (
<div style={ { zIndex: 10000000 } } className={ `flex absolute` }>
<div className={ `sidebar-holder flex flex-col bg-white max-h-screen ${ !dockSideBarOpen ? "open-nav" : "" }` } onWheel={ onWheel }>
<SidebarLogo />
<SidebarBuilder
onAddItem={ onAddItem }
editor={ editor }
updateModifications={ updateModifications }
selectedItems={ selectedItems }
/>
<SidebarFoot onDownloadImage={ onDownloadImage } onDownloadFile={ onDownloadFile } onUploadFile={ onUploadFile } fileRef={ fileRef } onEstimateModalOpen={ onEstimateModalOpen } />
</div>
<span className='bg-white h-fit' onClick={ () => toggleOpen( !dockSideBarOpen ) }>
{ !dockSideBarOpen ? (
<svg
className="block h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M4 6h16M4 12h16M4 18h16"
/>
</svg>
) : (
<svg
className="block h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
) }
</span>
</div>
)
}
+1
View File
@@ -0,0 +1 @@
export { DockSidebar } from './DockSidebar'
+464
View File
@@ -0,0 +1,464 @@
import React from "react";
import MkdSDK from "../utils/MkdSDK";
import {empty} from "../utils/utils";
const sdk = new MkdSDK();
const defaultImage = 'https://via.placeholder.com/150?text=%20';
const DynamicContentType = ({contentType, contentValue, setContentValue}) => {
const [previewUrl, setPreviewUrl] = React.useState(defaultImage);
const handleImageChange = async (e) => {
const formData = new FormData();
formData.append('file', e.target.files[0]);
try {
const result = await sdk.uploadImage(formData);
setPreviewUrl(result.url);
setContentValue(result.url);
}catch(err) {
console.error(err);
}
}
switch (contentType) {
case "text":
return (
<>
<textarea className={`shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline`}
rows={15}
placeholder="Content"
onChange={e => setContentValue(e.target.value)}
defaultValue={contentValue}
></textarea>
</>
)
case "image":
return (
<>
<img src={empty(contentValue) ? previewUrl : contentValue } alt="preview" height={150} width={150} />
<input
type="file"
onChange={handleImageChange}
className={`shadow appearance-none border block rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline`}
/>
</>
)
case "number":
return (
<input
type="number"
className={`shadow appearance-none border block rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline`}
onChange={e => setContentValue(e.target.value)}
defaultValue={contentValue}
/>
)
case "team-list":
return (
<TeamList setContentValue={setContentValue} contentValue={contentValue} />
)
case "image-list":
return (
<ImageList setContentValue={setContentValue} contentValue={contentValue} />
)
case "captioned-image-list":
return (
<CaptionedImageList setContentValue={setContentValue} contentValue={contentValue} />
)
case "kvp":
return (
<KeyValuePair setContentValue={setContentValue} contentValue={contentValue} />
)
default:
break;
}
}
export default DynamicContentType;
const ImageList = ({contentValue, setContentValue}) => {
let itemsObj = [
{key: '', value_type: 'image', value: null}
];
if (!empty(contentValue)) {
itemsObj = JSON.parse(contentValue);
}
const [items, setItems] = React.useState(itemsObj);
const handleImageChange = async (e) => {
const listKey = e.target.getAttribute('listkey');
const formData = new FormData();
formData.append('file', e.target.files[0]);
try {
const result = await sdk.uploadImage(formData);
setItems(oldItems => {
let updatedItems = oldItems.map((item, index) => {
if (index == listKey) {
item.value = result.url;
return item;
}
return item;
});
return updatedItems;
})
setContentValue(JSON.stringify(items))
}catch(err) {
console.error(err);
}
}
const handleKeyChange = (e) => {
const listKey = e.target.getAttribute('listkey');
setItems(oldItems => {
let updatedItems = oldItems.map((item, index) => {
if (index == listKey) {
item.key = e.target.value;
return item;
}
return item;
});
return updatedItems;
})
setContentValue(JSON.stringify(items))
}
return (
<div className="block">
{items.map( (item, index) => <div key={index*0.23}>
<img src={item.value !== null ? item.value : defaultImage} alt="preview" height={150} width={150} />
<div className="flex">
<input
className={`shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline`}
type="text" placeholder="key" listkey={index} onChange={handleKeyChange} defaultValue={item.key} />
<input
listkey={index}
type="file"
accept="image/*"
onChange={handleImageChange}
className={`shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline`}
/>
</div>
</div>)
}
<button
type="button"
className="bg-blue-400 hover:bg-blue-700 text-white font-bold py-1 px-2 my-4 rounded focus:outline-none focus:shadow-outline"
onClick={ e => setItems(old => [...old, {key: '', value_type: 'image', value: null}])}>
+
</button>
</div>
)
}
const CaptionedImageList = ({setContentValue, contentValue}) => {
let itemsObj = [
{key: '', value_type: 'image', value: null, caption: ''}
]
if (!empty(contentValue)) {
itemsObj = JSON.parse(contentValue);
}
const [items, setItems] = React.useState(itemsObj);
const handleImageChange = async (e) => {
const listKey = e.target.getAttribute('listkey');
const formData = new FormData();
formData.append('file', e.target.files[0]);
try {
const result = await sdk.uploadImage(formData);
setItems(oldItems => {
let updatedItems = oldItems.map((item, index) => {
if (index == listKey) {
item.value = result.url;
return item;
}
return item;
});
return updatedItems;
})
setContentValue(JSON.stringify(items))
}catch(err) {
console.error(err);
}
}
const handleKeyChange = (e) => {
const listKey = e.target.getAttribute('listkey');
setItems(oldItems => {
let updatedItems = oldItems.map((item, index) => {
if (index == listKey) {
item.key = e.target.value;
return item;
}
return item;
});
return updatedItems;
})
setContentValue(JSON.stringify(items))
}
const handleCaptionChange = (e) => {
const listKey = e.target.getAttribute('listkey');
setItems(oldItems => {
let updatedItems = oldItems.map((item, index) => {
if (index == listKey) {
item.caption = e.target.value;
return item;
}
return item;
});
return updatedItems;
})
setContentValue(JSON.stringify(items))
}
return (
<div className="block">
{items.map( (item, index) => <div key={index*0.23} >
<img src={item.value !== null ? item.value : defaultImage} alt="preview" height={150} width={150} />
<div className="flex">
<input
className={`shadow appearance-none border rounded w-full py-2 px-3 mr-2 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline`}
type="text" placeholder="Key" listkey={index} onChange={handleKeyChange} defaultValue={item.key}
/>
<input
listkey={index}
type="file"
accept="image/*"
onChange={handleImageChange}
className={`shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline`}
/>
</div>
<input
className={`shadow block appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline`}
type="text"
placeholder="Caption" listkey={index} onChange={handleCaptionChange} defaultValue={item.caption}
/>
</div>)
}
<button
type="button"
className="bg-blue-400 hover:bg-blue-700 text-white font-bold py-1 px-2 my-4 rounded focus:outline-none focus:shadow-outline"
onClick={ e => setItems(old => [...old, {key: '', value_type: 'image', value: null, caption: ''}])}>
+
</button>
</div>
)
}
const TeamList = ({setContentValue, contentValue}) => {
let itemsObj = [
{name: '', image: null, title: ''}
]
if (!empty(contentValue)) {
itemsObj = JSON.parse(contentValue);
}
const [items, setItems] = React.useState(itemsObj);
const handleImageChange = async (e) => {
const listKey = e.target.getAttribute('listkey');
const formData = new FormData();
formData.append('file', e.target.files[0]);
try {
const result = await sdk.uploadImage(formData);
setItems(oldItems => {
let updatedItems = oldItems.map((item, index) => {
if (index == listKey) {
item.image = result.url;
return item;
}
return item;
});
return updatedItems;
})
setContentValue(JSON.stringify(items))
}catch(err) {
console.error(err);
}
}
const handleNameChange = (e) => {
const listKey = e.target.getAttribute('listkey');
setItems(oldItems => {
let updatedItems = oldItems.map((item, index) => {
if (index == listKey) {
item.name = e.target.value;
return item;
}
return item;
});
return updatedItems;
})
setContentValue(JSON.stringify(items))
}
const handleTitleChange = (e) => {
const listKey = e.target.getAttribute('listkey');
setItems(oldItems => {
let updatedItems = oldItems.map((item, index) => {
if (index == listKey) {
item.title = e.target.value;
return item;
}
return item;
});
return updatedItems;
})
setContentValue(JSON.stringify(items))
}
return (
<div className="block my-4">
{items.map( (item, index) => <div key={index*0.23} className="my-4" >
{/* <div className="flex"> */}
<input
className={`shadow block appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline`}
type="text"
placeholder="Title" listkey={index} onChange={handleTitleChange} defaultValue={item.title}
/>
<input
className={`shadow appearance-none border rounded w-full py-2 px-3 mr-2 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline`}
type="text" placeholder="Name" listkey={index} onChange={handleNameChange} defaultValue={item.name}
/>
<img src={item.image !== null ? item.image : defaultImage} alt="preview" height={150} width={150} />
<input
listkey={index}
type="file"
accept="image/*"
onChange={handleImageChange}
className={`shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline`}
/>
{/* </div> */}
</div>)
}
<button
type="button"
className="bg-blue-400 hover:bg-blue-700 text-white font-bold py-1 px-2 my-4 rounded focus:outline-none focus:shadow-outline"
onClick={ e => setItems(old => [...old, {name: '', image: null, title: ''}])}>
+
</button>
</div>
)
}
const KeyValuePair = ({setContentValue, contentValue}) => {
let itemsObj = [
{key: '', value_type: 'text', value: ''}
]
if (!empty(contentValue)) {
itemsObj = JSON.parse(contentValue);
}
const [items, setItems] = React.useState(itemsObj);
const valueTypeMap = [
{
key: "text",
value: "Text"
},
{
key: "number",
value: "Number"
},
{
key: "json",
value: "JSON Object"
},
{
key: "url",
value: "URL"
}
]
const handleKeyChange = (e) => {
const listKey = e.target.getAttribute('listkey');
setItems(oldItems => {
let updatedItems = oldItems.map((item, index) => {
if (index == listKey) {
item.key = e.target.value;
return item;
}
return item;
});
return updatedItems;
})
setContentValue(JSON.stringify(items))
}
const handleValueChange = (e) => {
const listKey = e.target.getAttribute('listkey');
setItems(oldItems => {
let updatedItems = oldItems.map((item, index) => {
if (index == listKey) {
item.value = e.target.value;
return item;
}
return item;
});
return updatedItems;
})
setContentValue(JSON.stringify(items))
}
const handleValueTypeChange = (e) => {
const listKey = e.target.getAttribute('listkey');
setItems(oldItems => {
let updatedItems = oldItems.map((item, index) => {
if (index == listKey) {
item.value_type = e.target.value;
return item;
}
return item;
});
return updatedItems;
})
setContentValue(JSON.stringify(items))
}
return (
<div className="block">
{items.map( (item, index) => <div key={index*0.23} className="my-4" >
<input
className={`shadow appearance-none border rounded w-full py-2 px-3 mr-2 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline`}
type="text" placeholder="Key" listkey={index} onChange={handleKeyChange} defaultValue={item.key}
/>
<select
className={`shadow block border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline`}
listkey={index} onChange={handleValueTypeChange} defaultValue={item.value_type}>
{valueTypeMap.map( (type, index) => <option key={index*122} value={type.key}>{type.value}</option>)}
</select>
<input
className={`shadow block appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline`}
type="text"
required
placeholder="Value" listkey={index} onChange={handleValueChange} defaultValue={item.value}
/>
</div>)
}
<button
type="button"
className="bg-blue-400 hover:bg-blue-700 text-white font-bold py-1 px-2 my-4 rounded focus:outline-none focus:shadow-outline"
onClick={ e => setItems(old => [...old, {key: '', value_type: 'text', value: ''}])}>
+
</button>
</div>
)
}
@@ -0,0 +1,168 @@
import React, { memo, useState, useCallback } from 'react';
import { Modal } from 'Components/Modal';
import { InteractiveButton } from 'Components/InteractiveButton';
import { ArrowLeftIcon, ArrowRrightIcon, GreenTickIcon } from 'Assets/svgs';
import { dock_image, EstimateSteps, Tables, Truthy } from 'Utils/constants';
import { ContactInformation } from 'Components/ContactInformation';
import { LakeSurroundings } from 'Components/LakeSurroundings';
import { Comments } from 'Components/Comments';
import MkdSDK from 'Utils/MkdSDK';
const sdk = new MkdSDK()
const classes = {
modal: 'string',
modalDialog: 'relative bg-white w-[500px]',
modalContent: 'string',
modalHeader: 'string',
modalTitle: 'string',
modalBody: 'string',
modalFooter: 'string',
closeButtonClass: 'string',
saveButtonClass: 'string',
}
const EstimateModal = ( {
showEstimateModal,
modalCloseClick,
selectedItems,
dockImage,
} ) => {
const [ step, setStep ] = useState( EstimateSteps.ContactInformation )
const [ submitLoading, setSubmitLoading ] = useState( false )
const [ errorMessage, setErrorMessage ] = useState( null )
const [ hasDealer, setHasDealer ] = useState( Truthy.False )
// Contact Information
const [ dealer, setDealer ] = useState( 0 )
const [ firstName, setFirstName ] = useState( '' )
const [ lastName, setLastName ] = useState( '' )
const [ email, setEmail ] = useState( '' )
const [ phone, setPhone ] = useState( '' )
const [ lake, setLake ] = useState( '' )
const [ city, setCity ] = useState( '' )
const [ country, setCountry ] = useState( '' )
// Lake Surroundings
const [ dockConnection, setDockConnection ] = useState( '' )
const [ lakeBottom, setLakeBottom ] = useState( '' )
const [ willDockBoat, setWillDockBoat ] = useState( Truthy.False )
// Comments
const [ comments, setComments ] = useState( "" )
const onStepChange = useCallback( ( next ) => {
setStep( next )
}, [ step ] )
const onUpdateComment = useCallback( ( comment ) => {
setComments( comment )
}, [ comments ] )
const onSubmit = useCallback( () => {
( async () => {
try {
setSubmitLoading( true )
const quoteBody = {
first_name: firstName,
last_name: lastName,
email: email,
phone: phone,
lake: lake,
city: city,
dock_connection: dockConnection,
lake_bottom: lakeBottom,
country: country,
dock_image: dockImage,
has_dealer: hasDealer,
dealer_id: dealer,
will_dock_boat: willDockBoat,
comments: comments,
selected_items: JSON.stringify( selectedItems )
}
const result = await sdk.requestQuote( quoteBody )
if ( !result?.error ) {
setSubmitLoading( false )
setStep( EstimateSteps.InquirySubmitted )
}
} catch ( error ) {
setErrorMessage( error.message )
setSubmitLoading( false )
}
} )()
}, [ step, submitLoading, errorMessage, comments, hasDealer, dealer, firstName, lastName, email, phone, lake, city, country, dockConnection, lakeBottom, willDockBoat, selectedItems, dockImage ] )
const onClose = useCallback( () => {
setStep( EstimateSteps.ContactInformation )
modalCloseClick()
}, [ step ] )
return (
<Modal
title="REQUEST AN ESTIMATE"
isOpen={ showEstimateModal }
classes={ classes }
modalHeader
modalCloseClick={ modalCloseClick }
>
{/* ContactInformation */ }
<ContactInformation
step={ step }
onStepChange={ onStepChange }
hasDealer={ hasDealer }
onHasDealerChange={ setHasDealer }
onDealerChange={ setDealer }
setFirstName={ setFirstName }
setLastName={ setLastName }
setEmail={ setEmail }
setPhone={ setPhone }
setLake={ setLake }
setCity={ setCity }
setCountry={ setCountry }
/>
{/* LakeSurroundings */ }
<LakeSurroundings
step={ step }
onStepChange={ onStepChange }
setDockConnection={ setDockConnection }
setLakeBottom={ setLakeBottom }
setWillDockBoat={ setWillDockBoat }
/>
{/* Comments */ }
<Comments
step={ step }
onSubmit={ onSubmit }
onUpdateComment={ onUpdateComment }
onStepChange={ onStepChange }
errorMessage={ errorMessage }
submitLoading={ submitLoading }
comments={ comments }
/>
{/* InquirySubmitted */ }
<div className={ `${ step === EstimateSteps.InquirySubmitted ? '' : 'hidden' }` }>
<div>
<div className={ `flex items-center gap-x-2` }>
<GreenTickIcon />
<p className={ ` uppercase text-[#0F75BC] leading-[150%] tracking-wide text-[18px] font-bold` }>Inquiry Submitted</p>
</div>
</div>
<div className={ `flex w-full justify-center bg-transparent ` }>
<button
className={ `h-[44px] w-full rounded border-2 border-[#0F75BC] flex justify-center gap-x-2 items-center bg-[#0F75BC] text-[#ffffff] uppercase font-medium tracking-wide leading-[24px] text-[16px]` }
onClick={ onClose }
>
close
</button>
</div>
</div>
</Modal>
);
}
const EstimateModalMemo = memo( EstimateModal );
export { EstimateModalMemo as EstimateModal };
+1
View File
@@ -0,0 +1 @@
export { EstimateModal } from './EstimateModal'
@@ -0,0 +1,63 @@
import React from "react";
import { fabric } from "fabric";
function Rect({ left, top, width, height, fill }) {
const rectRef = React.useRef(null);
React.useEffect(() => {
const rect = new fabric.Rect({
left,
top,
width,
height,
fill,
});
rectRef.current = rect;
}, [left, top, width, height, fill]);
return <></>;
}
export default Rect;
// const addTestRect = () => {
// if (editor) {
// const rect = new fabric.Rect({
// left: 200,
// top: 50,
// width: 500,
// height: 50,
// fill: "red",
// });
// editorMemo.add(rect);
// const rect2 = new fabric.Rect({
// left: 250,
// top: 100,
// width: 200,
// height: 50,
// fill: "red",
// });
// editorMemo.add(rect2);
// const rect3 = new fabric.Rect({
// left: 300,
// top: 150,
// width: 200,
// height: 50,
// fill: "red",
// });
// editorMemo.add(rect3);
// const rect4 = new fabric.Rect({
// left: 350,
// top: 200,
// width: 200,
// height: 50,
// fill: "red",
// });
// editorMemo.add(rect4);
// }
// }
@@ -0,0 +1,48 @@
import React from "react";
export const InteractiveButton = ({
loading,
children,
type = "button",
className,
backgroundColor,
onClick,
}) => {
return (
<button
style={{ backgroundColor: backgroundColor && backgroundColor }}
onClick={onClick}
type={type}
disabled={loading}
className={`flex justify-center items-center gap-5 bg-[#0F75BC] ${className}`}
>
{loading ? (
<>
<svg
className="animate-spin -ml-1 mr-3 h-5 w-5 text-white inline"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>{" "}
{children}
</>
) : (
children
)}
</button>
);
};
@@ -0,0 +1 @@
export { InteractiveButton } from './InteractiveButton'
@@ -0,0 +1,141 @@
import React, { memo } from 'react';
import { ArrowLeftIcon, ArrowRrightIcon } from 'Assets/svgs';
import { EstimateSteps, Truthy } from 'Utils/constants';
const LakeSurroundings = ( { step, onStepChange, setWillDockBoat, setDockConnection, setLakeBottom } ) => {
const dockConnectionMap = [ 'Resting', 'Bolted', 'Other' ]
const lakeBottomMap = [ 'Rocky', 'Silty', 'Sandy' ]
// const dock_connection = { 0: 'Resting', 1: 'Bolted', 2: 'Other' }
// const lake_bottom = { 0: 'Rocky', 1: 'Silty', 2: 'Sandy' }
// const has_dealer = { 0: 'No', 1: 'Yes' }
// const will_dock_boat = { 0: 'No', 1: 'Yes' }
{/* LakeSurroundings */ }
return (
<div className={ `${ step === EstimateSteps.LakeSurrounding ? '' : 'hidden' }` }>
<div>
<button
className={ `text-[#4A5578] leading-[150%] tracking-tight text-[14px] font-medium flex items-center` }
onClick={ () => onStepChange( EstimateSteps.ContactInformation ) }
>
<ArrowLeftIcon /> Back
</button>
<div className={ `flex items-center gap-x-2` }>
<p className={ ` uppercase text-[#0F75BC] leading-[150%] tracking-wide text-[18px] font-bold` }>Lake Surroundings</p>
<span className={ `text-[#4A5578] leading-[150%] tracking-tight text-[14px] font-medium` }>(2 of 3)</span>
</div>
</div>
<div>
<fieldset className="cus-input">
<label
htmlFor="dockConnection"
className="text-[#4A5578] leading-[20px] tracking-tight text-[14px] font-medium"
>
<span className={ `text-red-600 mr-2` }>*</span> How Will The Dock Be Connected to The Shore?
</label>
<div className="relative mb-6">
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
{/* <img
className="w-5 h-5 object-contain"
src={ `` }
/> */}
</div>
<select
id="dockConnection"
className="bg-gray-50 border-2 border-[#D1D5DB] text-black text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-3 dark:bg-gray-200 dark:border-gray-600 dark:placeholder-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500 outline-none"
onChange={ ( e ) => setDockConnection( e.target.value ) }
>
<>
<option></option>
{ dockConnectionMap.map( ( dockConnection, index ) => (
<option key={ index } value={ index }>{ dockConnection }</option>
) ) }
</>
</select>
</div>
</fieldset>
<fieldset className="cus-input">
<label
htmlFor="lakeBottom"
className="text-[#4A5578] leading-[20px] tracking-tight text-[14px] font-medium"
>
<span className={ `text-red-600 mr-2 capitalize` }>*</span> What is the Lake Bottom Like?
</label>
<div className="relative mb-6">
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
{/* <img
className="w-5 h-5 object-contain"
src={ `` }
/> */}
</div>
<select
id="lakeBottom"
className="bg-gray-50 border-2 border-[#D1D5DB] text-black text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-3 dark:bg-gray-200 dark:border-gray-600 dark:placeholder-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500 outline-none"
onChange={ ( e ) => setLakeBottom( e.target.value ) }
>
<>
<option></option>
{ lakeBottomMap.map( ( lakeBottom, index ) => (
<option key={ index } value={ index }>{ lakeBottom }</option>
) ) }
</>
</select>
</div>
</fieldset>
<fieldset className="cus-input">
<label
// htmlFor="willDockBoat"
className="text-[#4A5578] leading-[20px] tracking-tight text-[14px] font-medium"
>
<span className={ `text-red-600 mr-2 capitalize` }>*</span>Will you be docking a boat to this system?
</label>
<div className="relative flex items-center justify-start mb-6">
<input
type="radio"
name="willDockBoat"
id="yesDockBoat"
className="bg-gray-50 border-2 border-[#D1D5DB] text-black text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-[44px] p-3 dark:bg-gray-200 dark:border-gray-600 dark:placeholder-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500 outline-none"
onChange={ () => setWillDockBoat( Truthy.True ) }
/>
<label
htmlFor="yesDockBoat"
className="text-[#4A5578] leading-[20px] tracking-tight text-[14px] font-medium"
>Yes
</label>
<input
type="radio"
name="willDockBoat"
id="noDockBoat"
className="bg-gray-50 border-2 border-[#D1D5DB] text-black text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-[44px] pl-10 p-3 dark:bg-gray-200 dark:border-gray-600 dark:placeholder-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500 outline-none"
onChange={ () => setWillDockBoat( Truthy.False ) }
/>
<label
htmlFor="noDockBoat"
className="text-[#4A5578] leading-[20px] tracking-tight text-[14px] font-medium"
>No
</label>
</div>
</fieldset>
</div>
<div className={ `flex w-full justify-center bg-transparent ` }>
<button
className={ `h-[44px] w-full rounded border-2 border-[#0F75BC] flex justify-center gap-x-2 items-center bg-[#0F75BC] text-[#ffffff] uppercase font-medium tracking-wide leading-[24px] text-[16px]` }
onClick={ () => onStepChange( EstimateSteps.Comments ) }
>
Continue <ArrowRrightIcon />
</button>
</div>
</div>
);
}
const LakeSurroundingsMemo = memo( LakeSurroundings );
export { LakeSurroundingsMemo as LakeSurroundings };
+1
View File
@@ -0,0 +1 @@
export { LakeSurroundings } from './LakeSurroundings'
+22
View File
@@ -0,0 +1,22 @@
import Indicator from "./LoadingIndicator";
const Loader = ({ style }) => {
return (
<div
style={{
display: "flex",
width: "100vw",
height: "100vh",
justifyContent: "center",
alignItems: "center",
...style
}}
>
<Indicator />
</div>
);
};
export default Loader;
+58
View File
@@ -0,0 +1,58 @@
import React from "react";
import { motion } from "framer-motion";
const containerVariant = {
start: {
transition: { staggerChildren: 0.2 }
},
end: {
transition: { staggerChildren: 0.2 }
}
};
const dotsVariants = {
start: {
y: "0%"
},
end: {
y: "100%"
}
};
const loadingTransition = {
duration: 0.4,
yoyo: Infinity,
ease: "easeIn"
};
export default function Indicator({ dotsClasses, size, style }) {
const dotsStyles = "block w-[9px] h-[9px] bg-slate-900 rounded-md shrink-0 " + dotsClasses;
return (
<motion.div
variants={containerVariant}
className={`flex justify-between items-center w-[40px] pb-[10px]`}
initial="start"
animate="end"
style={{ ...style }}
>
<motion.span
className={dotsStyles}
variants={dotsVariants}
transition={loadingTransition}
/>
<motion.span
className={dotsStyles}
variants={dotsVariants}
transition={loadingTransition}
/>
<motion.span
className={dotsStyles}
variants={dotsVariants}
transition={loadingTransition}
/>
</motion.div>
);
}
+38
View File
@@ -0,0 +1,38 @@
import React, { useEffect, useRef, useState, memo } from 'react';
import { CloseIcon } from 'Assets/svgs';
const Modal = ( {
children,
title,
isOpen = false,
modalCloseClick,
modalHeader,
classes
} ) => {
return (
<div
style={ { zIndex: 100000002 } }
className={ `modal-holder bg-[#00000099] items-center justify-center ${ isOpen ? 'flex' : 'hidden' }` }>
<div className={ `shadow p-4 bg-white rounded-lg ${ classes?.modalDialog }` }>
{ modalHeader &&
<div className={ `flex justify-between border-b pb-2` }>
<h5 className="font-bold text-center text-lg">{ title }</h5>
<div className="modal-close" onClick={ modalCloseClick }>
<CloseIcon />
</div>
</div>
}
<div className="mt-4">
{ children }
</div>
</div>
</div>
);
};
const ModalMemo = memo( Modal );
export { ModalMemo as Modal };
+1
View File
@@ -0,0 +1 @@
export { Modal } from './Modal'
+41
View File
@@ -0,0 +1,41 @@
import React from "react";
const PaginationBar = ({ currentPage, pageCount, pageSize, canPreviousPage, canNextPage, updatePageSize, previousPage, nextPage }) => {
return (
<>
<div className="flex justify-between ">
<div className="mt-2">
<span>
Page{" "}
<strong>
{+currentPage} of {pageCount}
</strong>{" "}
</span>
<select
className="mt-2"
value={pageSize}
onChange={(e) => {
updatePageSize(Number(e.target.value));
}}
>
{[5, 10, 20, 30, 40, 50].map((pageSize) => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</select>
</div>
{/* */}
<div>
<button onClick={previousPage} disabled={!canPreviousPage} className={`font-bold h-10 w-10 `}>
&#x02190;
</button>{" "}
<button onClick={nextPage} disabled={!canNextPage} className={`font-bold h-10 w-10 `}>
&#x02192;
</button>{" "}
</div>
</div>
</>
);
};
export default PaginationBar;
+15
View File
@@ -0,0 +1,15 @@
import React, { useState } from "react";
import { useNavigate } from "react-router";
import { AuthContext } from "../authContext";
export const PublicHeader = () => {
const { state, dispatch } = React.useContext(AuthContext);
const [isOpen, setIsOpen] = useState(false);
const navigate = useNavigate();
return <div>
</div>;
};
export default PublicHeader;
+18
View File
@@ -0,0 +1,18 @@
import React from 'react'
import { capitalize } from 'Utils/helper'
export const Field = ( { item, property } ) => {
return (
<>
{ item[ property ] ? (
<div className={ `flex items-center justify-between ${ property === "name" ? "text-sm" : "" }` }>
<div>{ capitalize( property ) }:</div>
<div>{ item[ property ] }{ " " }{ [ "length", "width" ].includes( property ) ? 'ft' : '' }</div>
</div> )
: null
}
</>
)
}
@@ -0,0 +1,48 @@
import { Chevron } from 'Assets/svgs'
import React from 'react'
import { Field } from './Field'
export const SelectedItem = ( { selectedItem } ) => {
const itemRef = React.useRef( null )
const [ open, setOpen ] = React.useState( false )
const onOpen = React.useCallback( ( isTrue ) => {
if ( isTrue ) {
itemRef.current.style.maxHeight = null
} else {
itemRef.current.style.maxHeight = `${ itemRef.current.scrollHeight }px`
}
setOpen( !isTrue )
}, [ open, itemRef ] )
return (
<div className={ ` mb-2` }>
<button onClick={ () => onOpen( open ) } className={ `flex items-center text-black max-w-full min-w-full mb-2 text-base border-b-2 border-b-state-200 py-2` }>
<div className={ `w-5` }>
<Chevron active={ open } />
</div>
<div className={ `truncate w-full text-left` }>
{ selectedItem.itemName } { `(${ selectedItem?.width }' x ${ selectedItem?.length }')` }
</div>
</button>
<div ref={ itemRef } className={ `max-h-0 overflow-hidden shadow-lg text-black` }>
<Field item={ selectedItem } property={ `name` } />
<Field item={ selectedItem } property={ `length` } />
<Field item={ selectedItem } property={ `width` } />
<Field item={ selectedItem } property={ `lift_range` } />
<Field item={ selectedItem } property={ `model` } />
<Field item={ selectedItem } property={ `no_of_cylinders` } />
<Field item={ selectedItem } property={ `weight_capacity` } />
<Field item={ selectedItem } property={ `category` } />
<Field item={ selectedItem } property={ `materials` } />
</div>
</div>
)
}
@@ -0,0 +1,15 @@
import React from 'react'
import { SelectedItem } from './SelectedItem'
export const SelectedItems = ( { selectedItems } ) => {
return (
<div className={ `gap-x-[8px] border-t-2 w-full px-3` }>
{ selectedItems?.length ? selectedItems?.map( ( item, index ) => (
<SelectedItem key={ index } selectedItem={ item } />
) )
: null }
</div>
)
}
+3
View File
@@ -0,0 +1,3 @@
export { SelectedItems } from './SelectedItems'
export { SelectedItem } from './SelectedItem'
export { Field } from './Field'
@@ -0,0 +1,51 @@
import React, { useCallback, useState } from "react";
import { Tabs } from "Components/Tabs";
import { Builder } from "Components/Builder";
import { SelectedItems } from "Components/SelectedItems";
import { TabNames } from "Utils/constants";
export const SidebarBuilder = ({
editor,
updateModifications,
selectedItems,
}) => {
const [activeTab, setActiveTab] = useState(TabNames.Builder);
// const [ramps, setRamps] = useState([]);
const onTabClick = useCallback(
(tab) => {
setActiveTab(tab);
},
[activeTab]
);
return (
<div
className={`flex flex-col max-h-[80%] grow relative overflow-auto gap-x-[8px] border-2 w-full`}
>
<Tabs
className={`flex gap-x-0 justify-center items-center min-h-[50px] max-h-[50px] h-[50px] w-full`}
activeTab={activeTab}
onTabClick={onTabClick}
/>
{activeTab === TabNames.Builder ? (
<Builder
editor={editor}
updateModifications={updateModifications}
// ramps={ramps}
// setRamps={setRamps}
/>
) : null}
{activeTab === TabNames.SelectedItems ? (
<SelectedItems selectedItems={selectedItems} />
) : null}
</div>
);
};
{
/* <div className={ `grow flex flex-col justify-center items-start bg-transparent` }>
</div> */
}
+1
View File
@@ -0,0 +1 @@
export { SidebarBuilder } from './SidebarBuilder'
@@ -0,0 +1,38 @@
import React from 'react'
import { DownloadIcon, UploadIcon } from 'Assets/svgs'
export const SidebarFoot = ( { onDownloadImage, onDownloadFile, fileRef, onUploadFile, onEstimateModalOpen } ) => {
return (
<div className={ `flex flex-col justify-end items-end m-auto h-fit z-50 w-full py-2 px-[20px] bg-white` }>
<button
onClick={ () => fileRef.current.click() }
className={ `text-[#000000] flex justify-start items-center gap-x-5 w-full h-[45px] rounded` }
>
<UploadIcon /> Upload File
<input ref={ fileRef } type="file" hidden onChangeCapture={ ( e ) => onUploadFile( e ) } accept={ `.dock` } />
</button>
<button
className={ `text-[#000000] flex justify-start items-center gap-x-5 w-full h-[45px] rounded` }
onClick={ onDownloadFile }
>
<DownloadIcon /> Download File
</button>
<button
className={ `text-[#000000] flex justify-start items-center gap-x-5 w-full h-[45px] rounded` }
onClick={ onDownloadImage }
>
<DownloadIcon /> Download Image
</button>
<button
className={ `text-white flex justify-center items-center w-full h-[35px] rounded bg-[#0F75BC]` }
onClick={ onEstimateModalOpen }
>
GET ESTIMATE
</button>
</div>
)
}
+1
View File
@@ -0,0 +1 @@
export { SidebarFoot } from './SidebarFoot'
@@ -0,0 +1,15 @@
import { BrandLogo } from 'Assets/images'
import React from 'react'
export const SidebarLogo = () =>
{
return (
<div className={ `flex h-[96px] px-[12px] py-[20px] gap-x-[8px]` }>
<img src={ BrandLogo } alt="Brand Logo" width={ 43.87 } height={ 56 } className={ `relative h-[56px] w-[43.87px] rounded-[8px]` } />
<div className={ `grow flex flex-col justify-center items-start bg-transparent` }>
<div className={ `w-full text-[#005C9B] font-bold text-[14px] leading-[150%] tracking-[-0.02em]` }>Paradise Dock & Lift Inc.</div>
<div className={ `w-full uppercase text-[#30374F] font-bold text-[14px] leading-[150%] tracking-[-0.02em]` }>Dock builder tool</div>
</div>
</div>
)
}
+1
View File
@@ -0,0 +1 @@
export { SidebarLogo } from './SidebarLogo'
+32
View File
@@ -0,0 +1,32 @@
import React from "react";
import { GlobalContext } from "../globalContext";
const SnackBar = () => {
const { state, dispatch } = React.useContext(GlobalContext);
const show = state.globalMessage.length > 0;
return show ? (
<div id="mkd-toast" className="absolute top-5 right-5 flex items-center w-full max-w-xs p-4 text-gray-500 bg-slate-100 rounded-lg shadow dark:text-gray-400" role="alert">
<div className="text-sm font-normal">{state.globalMessage}</div>
<div className="flex items-center ml-auto space-x-2">
<button
type="button"
className="bg-white text-gray-400 hover:text-gray-900 rounded-lg focus:ring-2 focus:ring-gray-300 p-1.5 hover:bg-gray-100 inline-flex h-8 w-8 dark:text-gray-500 dark:hover:text-white dark:hover:bg-gray-700"
aria-label="Close"
onClick={() => {
dispatch({ type: "SNACKBAR", payload: { message: "" } });
}}
>
<span className="sr-only">Close</span>
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clipRule="evenodd"
></path>
</svg>
</button>
</div>
</div>
) : null;
};
export default SnackBar;
+10
View File
@@ -0,0 +1,10 @@
import React from 'react'
export const Tab = ( { name, className, active, onTabClick } ) => {
return (
<button className={ `${ className } ${ active ? "border-b-2 border-b-[#0F75BC]" : "" }` } onClick={ () => onTabClick( name ) }>
{ name }
</button>
)
}
+1
View File
@@ -0,0 +1 @@
export { Tab } from './Tab'
+95
View File
@@ -0,0 +1,95 @@
import React from 'react'
export const Table = ( { className, tableColumns, tableData } ) => {
return (
<div className={ `shadow overflow-x-auto border-b border-gray-200 ${ className }` }>
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
{ tableColumns.map( ( column, i ) => (
<th
key={ i }
scope="col"
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
onClick={ () => onSort( i ) }
>
{ column.header }
<span>
{ column.isSorted
? column.isSortedDesc
? " ▼"
: " ▲"
: "" }
</span>
</th>
) ) }
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{ tableData.map( ( row, i ) => {
return (
<tr key={ i }>
{ tableColumns.map( ( cell, index ) => {
if ( cell.accessor == "" ) {
return (
<td
key={ index }
className="px-6 py-4 whitespace-nowrap"
>
{/* <button
className="text-xs"
onClick={ () => {
navigate( "/admin/edit-quotes/" + row.id, {
state: row,
} );
} }
>
{ " " }
Edit
</button> */}
<button
className="text-xs px-1 text-blue-500"
onClick={ () => {
navigate( "/admin/view-quotes/" + row.id, {
state: row,
} );
} }
>
{ " " }
View
</button>
<button
className="text-xs px-1 text-red-500"
onClick={ () => deleteItem( row.id ) }
>
{ " " }
Delete
</button>
</td>
);
}
if ( cell.mappingExist ) {
return (
<td
key={ index }
className="px-6 py-4 whitespace-nowrap"
>
{ cell.mappings[ row[ cell.accessor ] ] }
</td>
);
}
return (
<td key={ index } className="px-6 py-4 whitespace-nowrap">
{ [ "image", "top_view", "thumbnail" ].includes( cell.accessor ) ? <img className={ `rounded-md` } src={ row[ cell.accessor ] } /> : row[ cell.accessor ] }
</td>
);
} ) }
</tr>
);
} ) }
</tbody>
</table>
</div>
)
}
+1
View File
@@ -0,0 +1 @@
export { Table } from './Table'
+25
View File
@@ -0,0 +1,25 @@
import React from 'react'
import { Tab } from 'Components/Tab'
import { TabNames } from 'Utils/constants'
export const Tabs = ( { activeTab, className, onTabClick } ) => {
return (
<div className={ `${ className }` }>
<Tab
name={ TabNames.Builder }
className={ `w-1/2 text-[#111322] h-full text-[14px] leading-[150%] font-medium tracking-tight` }
active={ activeTab === TabNames.Builder ? true : false }
onTabClick={ onTabClick }
/>
<Tab
name={ TabNames.SelectedItems }
className={ `w-1/2 text-[#111322] h-full text-[14px] leading-[150%] font-medium tracking-tight` }
active={ activeTab === TabNames.SelectedItems ? true : false }
onTabClick={ onTabClick }
/>
</div>
)
}
+1
View File
@@ -0,0 +1 @@
export { Tabs } from './Tabs'
+52
View File
@@ -0,0 +1,52 @@
import React from "react";
import { GlobalContext } from "../globalContext";
const TopHeader = () => {
const { state, dispatch } = React.useContext(GlobalContext);
let { isOpen } = state;
let toggleOpen = (open) =>
dispatch({
type: "OPEN_SIDEBAR",
payload: { isOpen: open },
});
return (
<div className="page-header shadow">
<span onClick={() => toggleOpen(!isOpen)}>
{!isOpen ? (
<svg
className="block h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M4 6h16M4 12h16M4 18h16"
/>
</svg>
) : (
<svg
className="block h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
)}
</span>
</div>
);
};
export default TopHeader;
@@ -0,0 +1,77 @@
import React, { memo, useState, useCallback } from 'react';
import { Modal } from 'Components/Modal';
import { InteractiveButton } from 'Components/InteractiveButton';
// import { ArrowLeftIcon, ArrowRrightIcon, GreenTickIcon } from 'Assets/svgs';
// import { dock_image, EstimateSteps, Tables, Truthy } from 'Utils/constants';
// import { ContactInformation } from 'Components/ContactInformation';
// import { LakeSurroundings } from 'Components/LakeSurroundings';
// import { Comments } from 'Components/Comments';
import MkdSDK from 'Utils/MkdSDK';
const sdk = new MkdSDK()
const classes = {
modal: 'string',
modalDialog: 'relative bg-white w-[800px]',
modalContent: 'string',
modalHeader: 'string',
modalTitle: 'string',
modalBody: 'string',
modalFooter: 'string',
closeButtonClass: 'string',
saveButtonClass: 'string',
}
const ViewDockImageModal = ( {
showViewDockImageModal,
modalCloseClick,
dockImage
} ) => {
// const [ step, setStep ] = useState( EstimateSteps.ContactInformation )
// const [ submitLoading, setSubmitLoading ] = useState( false )
// const [ errorMessage, setErrorMessage ] = useState( null )
// const [ hasDealer, setHasDealer ] = useState( Truthy.False )
const onDownloadClick = useCallback( () => {
const Anchor = document.createElement( "a" )
// console.log( "viewDockImageModal", dockImage )
Anchor.href = dockImage;
Anchor.download = `dock_image_snapshot.png`;
Anchor.click();
// modalCloseClick()
}, [ dockImage ] )
return (
<Modal
title="DOCK IMAGE"
isOpen={ showViewDockImageModal }
classes={ classes }
modalHeader
modalCloseClick={ modalCloseClick }
>
<div>
<div className={ `w-full h-[400px] rounded-xl my-3` }>
<img src={ dockImage } className={ `w-full h-full rounded-xl` } alt="dock_image" />
</div>
<div className={ `w-full flex gap-x-2 justify-between items-center` }>
<InteractiveButton
className={ `h-[44px] w-full rounded border-2 border-[#0F75BC] flex justify-center gap-x-2 items-center hover:scale-95 hover:border-[#084c7c] text-[#ffffff] uppercase font-medium tracking-wide leading-[24px] text-[16px]` }
backgroundColor={ `#0F75BC` }
onClick={ onDownloadClick }
>
Download
</InteractiveButton>
</div>
</div>
</Modal>
);
}
const ViewDockImageModalMemo = memo( ViewDockImageModal );
export { ViewDockImageModalMemo as ViewDockImageModal };
@@ -0,0 +1 @@
export { ViewDockImageModal } from './ViewDockImageModal'
+6
View File
@@ -0,0 +1,6 @@
export { default as AddButton } from './AddButton'
export { default as AdminHeader } from './AdminHeader'
export { default as PaginationBar } from './PaginationBar'
export { default as PublicHeader } from './PublicHeader'
export { default as SnackBar } from './SnackBar'
export { default as TopHeader } from './TopHeader'