diff --git a/.env.sample b/.env.sample deleted file mode 100644 index c2d4e1fb79..0000000000 --- a/.env.sample +++ /dev/null @@ -1,5 +0,0 @@ -REACT_APP_API_URL=http://localhost:4000 -NODE_ENV=development -MONGO_URL=mongodb://localhost:27017/erxes -NODE_OPTIONS=--max-old-space-size=1536 -ENABLED_PLUGINS=frontline \ No newline at end of file diff --git a/frontend/plugins/frontline_ui/src/modules/integrations/erxes-messenger/components/EMPreviewIntro.tsx b/frontend/plugins/frontline_ui/src/modules/integrations/erxes-messenger/components/EMPreviewIntro.tsx index d4de6866a7..d8c726a4dc 100644 --- a/frontend/plugins/frontline_ui/src/modules/integrations/erxes-messenger/components/EMPreviewIntro.tsx +++ b/frontend/plugins/frontline_ui/src/modules/integrations/erxes-messenger/components/EMPreviewIntro.tsx @@ -11,7 +11,7 @@ import { EMGreetingAvatar } from '@/integrations/erxes-messenger/components/EMGr export const ActiveUsers = () => { const { members } = useMembersInlineContext(); return ( -
+
{members.map((member) => ( @@ -27,9 +27,10 @@ export const ActiveUsers = () => { export const EMPreviewIntro = () => { const greeting = useAtomValue(erxesMessengerSetupGreetingAtom); const hours = useAtomValue(erxesMessengerSetupHoursAtom); + return ( <> -
+
-
+
{greeting?.links?.map( (link) => !!link && ( @@ -60,22 +61,22 @@ export const EMPreviewIntro = () => {

{greeting?.title || 'Welcome'}

-

+

{greeting?.message || 'Welcome to Erxes Messenger'}

-
-
+
+
Recent conversations
+ + + {dropdownItems.map((item) => ( +
{ + if ((item as any).disabled) return; + item.onClick(); + }} + > + {item.icon} +

+ {item.label} +

+
+ ))} +
+ + ); +}; diff --git a/frontend/plugins/tourism_ui/src/modules/tms/components/BranchCard.tsx b/frontend/plugins/tourism_ui/src/modules/tms/components/BranchCard.tsx new file mode 100644 index 0000000000..1af1676e0c --- /dev/null +++ b/frontend/plugins/tourism_ui/src/modules/tms/components/BranchCard.tsx @@ -0,0 +1,90 @@ +import { IconCalendarPlus, IconPhoto } from '@tabler/icons-react'; +import { IBranch } from '@/tms/types/branch'; +import { format } from 'date-fns'; +import { Avatar, readImage } from 'erxes-ui'; +import { ActionMenu } from './ActionMenu'; + +interface BranchCardProps { + branch: IBranch; + onEdit: (branchId: string) => void; + onDuplicate: (branchId: string) => void; + onDelete: (branchId: string) => void; + duplicateLoading: boolean; +} + +export const BranchCard = ({ + branch, + onEdit, + onDuplicate, + onDelete, + duplicateLoading, +}: BranchCardProps) => { + return ( +
+
+
+
+
+

+ {branch.name || 'Unnamed Branch'} +

+
+ + onEdit(branch._id)} + onDuplicate={() => onDuplicate(branch._id)} + onDelete={() => onDelete(branch._id)} + duplicateLoading={duplicateLoading} + /> +
+ +
+
+ {branch.uiOptions?.logo ? ( + {branch.name + ) : ( +
+ +
+ )} +
+
+ +
+
+ + + Created:{' '} + {branch.createdAt + ? format(new Date(branch.createdAt), 'dd MMM yyyy') + : 'N/A'} + +
+ + + {branch.user?.details?.avatar ? ( + + ) : null} + + {branch.user?.details?.fullName + ?.split(' ')[0] + ?.charAt(0) + ?.toUpperCase() || 'A'} + + +
+
+
+
+ ); +}; diff --git a/frontend/plugins/tourism_ui/src/modules/tms/components/BranchList.tsx b/frontend/plugins/tourism_ui/src/modules/tms/components/BranchList.tsx index c3906e86b8..1f5e1ce2dc 100644 --- a/frontend/plugins/tourism_ui/src/modules/tms/components/BranchList.tsx +++ b/frontend/plugins/tourism_ui/src/modules/tms/components/BranchList.tsx @@ -1,161 +1,123 @@ -import { - IconCalendarPlus, - IconEdit, - IconCopy, - IconWorld, - IconTrash, - IconChevronDown, -} from '@tabler/icons-react'; -import { useState, useRef, useEffect } from 'react'; +import { useState } from 'react'; import { useBranchList } from '@/tms/hooks/BranchList'; import { useBranchRemove } from '@/tms/hooks/BranchRemove'; +import { useBranchDuplicate } from '@/tms/hooks/BranchDuplicate'; +import { useBranchEdit } from '@/tms/hooks/BranchEdit'; import { IBranch } from '@/tms/types/branch'; -import { format } from 'date-fns'; import { EmptyList } from './EmptyList'; -import { toast } from 'erxes-ui'; +import { BranchCard } from './BranchCard'; +import { ConfirmationDialog } from './ConfirmationDialog'; +import { Sheet, Spinner } from 'erxes-ui'; +import CreateTmsForm from './CreateTmsForm'; export const BranchList = () => { - const { list, loading, error } = useBranchList(); - const { removeBranchById, loading: removeLoading } = useBranchRemove(); + const { list, loading, error, refetch } = useBranchList({ + orderBy: { createdAt: -1 }, + }); - const [isMenuOpen, setIsMenuOpen] = useState(null); - const menuRef = useRef(null); + const { handleDeleteBranch, loading: removeLoading } = useBranchRemove(); - const toggleMenu = (index: number) => { - setIsMenuOpen(isMenuOpen === index ? null : index); - }; + const { handleDuplicateBranch, loading: duplicateLoading } = + useBranchDuplicate(); - const handleDeleteBranch = async (branchId: string) => { - if (window.confirm('Are you sure you want to delete this branch?')) { - setIsMenuOpen(null); + const { editingBranch, handleEditBranch, closeEditDialog } = useBranchEdit(); - try { - const success = await removeBranchById(branchId); + const [deleteDialogOpen, setDeleteDialogOpen] = useState(null); - if (success) { - toast.success('Branch deleted successfully'); - } else { - toast.error('Failed to delete branch'); - } - } catch (error) { - toast.error('Failed to delete branch'); - } + const [duplicateDialogOpen, setDuplicateDialogOpen] = useState( + null, + ); + + const onDuplicateConfirm = async () => { + setDuplicateDialogOpen(null); + const branch = list?.find((b) => b._id === duplicateDialogOpen); + if (branch) { + await handleDuplicateBranch(branch, refetch); } }; - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if (menuRef.current && !menuRef.current.contains(event.target as Node)) { - setIsMenuOpen(null); - } - }; + const onDeleteConfirm = async () => { + setDeleteDialogOpen(null); + if (deleteDialogOpen) { + await handleDeleteBranch(deleteDialogOpen, refetch); + } + }; + + if (loading) + return ( +
+ +
+ ); - document.addEventListener('mousedown', handleClickOutside); - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; - }, [menuRef]); + if (error) { + return {error.message}; + } - if (loading) return
Loading...
; - if (error) return
Error loading branches
; if (!list || list.length === 0) return ; return ( -
-
- {list.map((branch: IBranch, index: number) => ( -
-
-
-
-
-

- {branch.name || 'Unnamed Branch'} -

-
- -
- - - {isMenuOpen === index && ( -
-
- -

- Manage -

-
-
- -

- Duplicate -

-
-
- -

- Visit website -

-
-
handleDeleteBranch(branch._id)} - > - -

- Delete -

-
-
- )} -
-
+ <> +
+
+
+ {list.map((branch: IBranch) => ( + + ))} +
+
+
-
- {/* Placeholder image since we don't have an image property in the data */} -
- - {branch.name?.charAt(0) || 'B'} - -
-
+ { + if (!open) { + closeEditDialog(); + } + }} + > + {editingBranch && ( + { + if (!open) { + closeEditDialog(); + } + }} + onSuccess={() => { + closeEditDialog(); + }} + refetch={refetch} + /> + )} + -
-
- - - Created:{' '} - {branch.createdAt - ? format(new Date(branch.createdAt), 'dd MMM yyyy') - : 'N/A'} - -
+ !open && setDuplicateDialogOpen(null)} + type="duplicate" + branchName={ + list?.find((b) => b._id === duplicateDialogOpen)?.name || '' + } + loading={duplicateLoading} + onConfirm={onDuplicateConfirm} + /> - {branch.user?.details?.avatar && ( - {branch.user.details.fullName - )} -
-
-
-
- ))} -
-
+ !open && setDeleteDialogOpen(null)} + type="delete" + branchName={list?.find((b) => b._id === deleteDialogOpen)?.name || ''} + loading={removeLoading} + onConfirm={onDeleteConfirm} + /> + ); }; diff --git a/frontend/plugins/tourism_ui/src/modules/tms/components/ConfirmationDialog.tsx b/frontend/plugins/tourism_ui/src/modules/tms/components/ConfirmationDialog.tsx new file mode 100644 index 0000000000..6bc35d5d95 --- /dev/null +++ b/frontend/plugins/tourism_ui/src/modules/tms/components/ConfirmationDialog.tsx @@ -0,0 +1,83 @@ +import { IconCopy, IconTrash } from '@tabler/icons-react'; +import { Dialog, Button } from 'erxes-ui'; + +interface ConfirmationDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + type: 'duplicate' | 'delete'; + branchName: string; + loading: boolean; + onConfirm: () => void; +} + +export const ConfirmationDialog = ({ + open, + onOpenChange, + type, + branchName, + loading, + onConfirm, +}: ConfirmationDialogProps) => { + const isDuplicate = type === 'duplicate'; + const isDelete = type === 'delete'; + + return ( + + + +
+
+ {isDuplicate ? ( + + ) : ( + + )} +
+ + {isDuplicate ? 'Duplicate Branch' : 'Delete Branch'} + +
+
+
+
+

+ {isDuplicate + ? 'This will create a new branch with copied settings' + : 'Warning: This action cannot be undone'} +

+
+

+ Are you sure you want to{' '} + {isDuplicate ? 'create a duplicate of' : 'permanently delete'}{' '} + "{branchName}"?{' '} + {isDuplicate + ? 'A new branch will be created with all the same configurations.' + : 'All associated data will be lost forever.'} +

+
+ + + + +
+
+ ); +}; diff --git a/frontend/plugins/tourism_ui/src/modules/tms/components/CreateTmsForm.tsx b/frontend/plugins/tourism_ui/src/modules/tms/components/CreateTmsForm.tsx index 0ff432c76c..dd9aca0b11 100644 --- a/frontend/plugins/tourism_ui/src/modules/tms/components/CreateTmsForm.tsx +++ b/frontend/plugins/tourism_ui/src/modules/tms/components/CreateTmsForm.tsx @@ -1,93 +1,154 @@ import { TmsCreateSheetHeader } from '@/tms/components/CreateTmsSheet'; -import { Sheet, Form, useToast } from 'erxes-ui'; +import { Sheet, Form, Preview, Separator, Spinner, Resizable } from 'erxes-ui'; import { useForm } from 'react-hook-form'; import { TmsFormSchema, TmsFormType } from '@/tms/constants/formSchema'; import { zodResolver } from '@hookform/resolvers/zod'; -import { ApolloError } from '@apollo/client'; import { TmsInformationFields } from '@/tms/components/TmsInformationFields'; -import Preview from '@/tms/components/Preview'; -import { useCreateBranch } from '../hooks/CreateBranch'; +import { useBranchDetail } from '@/tms/hooks/BranchDetail'; +import { useBranchSubmit } from '@/tms/hooks/useBranchSubmit'; +import { useEffect } from 'react'; +import { useAtom, useSetAtom } from 'jotai'; +import { tmsFormAtom } from '@/tms/atoms/formAtoms'; +import { currentStepAtom } from '@/tms/states/tmsInformationFieldsAtoms'; + +interface PermissionConfig { + type: string; + title: string; + icon: string; + config?: string; +} const CreateTmsForm = ({ + branchId, onOpenChange, onSuccess, + refetch, }: { + branchId?: string; onOpenChange?: (open: boolean) => void; onSuccess?: () => void; + refetch?: () => Promise; }) => { - const { createBranch } = useCreateBranch(); + const { branchDetail, loading: detailLoading } = useBranchDetail({ + id: branchId || '', + }); + + const isEditMode = !!branchId; + const [formData] = useAtom(tmsFormAtom); + const setFormAtom = useSetAtom(tmsFormAtom); + const setCurrentStep = useSetAtom(currentStepAtom); + const form = useForm({ resolver: zodResolver(TmsFormSchema), defaultValues: { - name: '', - color: '#4F46E5', - logo: '', - favIcon: '', - generalManeger: '', - manegers: [], - payment: '', - token: '', - otherPayments: [], + name: formData.name, + color: formData.color, + logo: formData.logo, + favIcon: formData.favIcon, + generalManager: formData.generalManager, + managers: formData.managers, + payment: formData.payment, + token: formData.token, + otherPayments: formData.otherPayments, }, }); - const watchedValues = form.watch(); - const { toast } = useToast(); + useEffect(() => { + if (isEditMode) { + setCurrentStep(1); + } + }, [isEditMode, setCurrentStep]); + + const { handleSubmit, isLoading } = useBranchSubmit({ + isEditMode, + branchId, + form, + refetch, + onOpenChange, + onSuccess, + }); + + useEffect(() => { + if (branchDetail) { + const updatedFormData = { + name: branchDetail.name || '', + color: branchDetail.uiOptions?.colors?.primary || '#4F46E5', + logo: branchDetail.uiOptions?.logo || '', + favIcon: branchDetail.uiOptions?.favIcon || '', + generalManager: branchDetail.generalManagerIds || [], + managers: branchDetail.managerIds || [], + payment: Array.isArray(branchDetail.paymentIds) + ? branchDetail.paymentIds[0] || '' + : '', + token: branchDetail.erxesAppToken || '', + otherPayments: Array.isArray(branchDetail.permissionConfig) + ? branchDetail.permissionConfig.map((config: PermissionConfig) => ({ + type: config.type || '', + title: config.title || '', + icon: config.icon || '', + config: config.config || '', + })) + : [], + }; + + setFormAtom(updatedFormData); + form.reset(updatedFormData); + } + }, [branchDetail, setFormAtom, form]); - const onSubmit = (data: TmsFormType) => { - createBranch({ - variables: { - name: data.name, - user1Ids: data.generalManeger ? [data.generalManeger] : undefined, - user2Ids: data.manegers || undefined, - paymentIds: data.payment ? [data.payment] : undefined, - token: data.token, - erxesAppToken: '', - uiOptions: { - logo: data.logo, - favIcon: data.favIcon, - colors: { - primary: data.color, - }, - }, - }, - onError: (e: ApolloError) => { - toast({ - title: 'Error', - description: e.message, - variant: 'destructive', - }); - }, - onCompleted: () => { - toast({ - title: 'Success', - description: 'Branch created successfully', - }); - form.reset(); - onOpenChange?.(false); - onSuccess?.(); - }, - }); - }; + if (isEditMode && detailLoading) { + return ; + } return ( -
- + + {isEditMode ? ( + + Edit Tour Management System + + + ) : ( - - - - - - + )} + +
+ + + + + + + + + +
+ +
+ + +
+
+
+
+ +
); }; diff --git a/frontend/plugins/tourism_ui/src/modules/tms/components/CreateTmsSheet.tsx b/frontend/plugins/tourism_ui/src/modules/tms/components/CreateTmsSheet.tsx index 2b618a4d42..8cdf7dfa2b 100644 --- a/frontend/plugins/tourism_ui/src/modules/tms/components/CreateTmsSheet.tsx +++ b/frontend/plugins/tourism_ui/src/modules/tms/components/CreateTmsSheet.tsx @@ -2,41 +2,35 @@ import { IconPlus } from '@tabler/icons-react'; import { Button, Sheet } from 'erxes-ui'; import { useState } from 'react'; import CreateTmsForm from '@/tms/components/CreateTmsForm'; +import { useSetAtom } from 'jotai'; +import { currentStepAtom } from '~/modules/tms/states/tmsInformationFieldsAtoms'; export const TmsCreateSheet = () => { const [open, setOpen] = useState(false); + const setCurrentStep = useSetAtom(currentStepAtom); - const onOpen = () => { - setOpen(true); + const handleOpenChange = (openState: boolean) => { + setOpen(openState); + setCurrentStep(1); }; - const onClose = () => { - setOpen(false); - }; return ( - (open ? onOpen() : onClose())}> + - { - e.preventDefault(); - }} - > - - + ); }; export const TmsCreateSheetHeader = () => { return ( - + Create Tour Management System diff --git a/frontend/plugins/tourism_ui/src/modules/tms/components/EmptyList.tsx b/frontend/plugins/tourism_ui/src/modules/tms/components/EmptyList.tsx index 22cf1607c0..5a0649ff48 100644 --- a/frontend/plugins/tourism_ui/src/modules/tms/components/EmptyList.tsx +++ b/frontend/plugins/tourism_ui/src/modules/tms/components/EmptyList.tsx @@ -1,23 +1,24 @@ -import { TmsCreateSheet } from '@/tms/components/CreateTmsSheet'; +import { getPluginAssetsUrl } from 'erxes-ui'; +import { TmsCreateSheet } from '~/modules/tms/components/CreateTmsSheet'; export const EmptyList = () => { return ( -
-
-
+
+
+
tourism
-

+

Tour management system

-

+

A tour management system is software designed to organize and manage tourism-related activities. It helps streamline trip planning, booking, payment management, customer information, and travel diff --git a/frontend/plugins/tourism_ui/src/modules/tms/components/PaymentIcon.tsx b/frontend/plugins/tourism_ui/src/modules/tms/components/PaymentIcon.tsx new file mode 100644 index 0000000000..af0c8428b0 --- /dev/null +++ b/frontend/plugins/tourism_ui/src/modules/tms/components/PaymentIcon.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import { + IconCreditCard, + IconCashBanknote, + IconBuilding, + IconPhone, + IconBrandVisa, + IconBrandMastercard, + IconFile, +} from '@tabler/icons-react'; + +export const paymentIconOptions = [ + { value: 'visa', label: 'Visa' }, + { value: 'mastercard', label: 'Mastercard' }, + { value: 'cash', label: 'QPay' }, + { value: 'bank', label: 'SocialPay' }, + { value: 'credit-card', label: 'Credit Card' }, + { value: 'mobile', label: 'Mobile Payment' }, +]; + +interface PaymentIconProps { + iconType: string; + size?: number; + className?: string; +} + +const PaymentIcon: React.FC = ({ + iconType, + size = 16, + className = '', +}) => { + const getIcon = () => { + switch (iconType) { + case 'credit-card': + return ; + case 'cash': + return ; + case 'bank': + return ; + case 'mobile': + return ; + case 'visa': + return ( + + ); + case 'mastercard': + return ( + + ); + case 'sign-alt': + return ; + default: + return ; + } + }; + + return getIcon(); +}; + +export default PaymentIcon; diff --git a/frontend/plugins/tourism_ui/src/modules/tms/components/Preview.tsx b/frontend/plugins/tourism_ui/src/modules/tms/components/Preview.tsx deleted file mode 100644 index 53d4896ef9..0000000000 --- a/frontend/plugins/tourism_ui/src/modules/tms/components/Preview.tsx +++ /dev/null @@ -1,217 +0,0 @@ -import { - IconDeviceDesktop, - IconDeviceMobile, - IconDeviceTablet, - IconWifi, - IconAntennaBars5, - IconBattery, -} from '@tabler/icons-react'; -import { Button, Input } from 'erxes-ui'; -import { useState } from 'react'; -import { TmsFormType } from '@/tms/constants/formSchema'; - -type DeviceType = 'desktop' | 'mobile' | 'tablet'; - -interface PreviewProps { - formData?: Partial; -} - -export default function Preview({ formData }: PreviewProps) { - const [activeDevice, setActiveDevice] = useState('desktop'); - - const tourName = formData?.name || 'Таны тур оператор'; - const themeColor = formData?.color || '#4F46E5'; - const logoUrl = - formData?.logo || - 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTbvwVAC3F5xurW6mtfMrEoeWvuQpisg17tNg&s'; - - const renderLoginForm = () => { - return ( -

-
- Company Logo -
- -
-

- Sign in to your account -

-

- Enter your email and password below to access your account. -

-
- -
- -
- -
- - -
- -
-
- - Forgot password? -
- -
- - -
- ); - }; - - const renderDeviceFrame = () => { - switch (activeDevice) { - case 'desktop': - return ( -
-
-
-
-
-
-
-
-
-
-
- {renderLoginForm()} -
-
-
- ); - - case 'mobile': - return ( -
- {/* Status bar */} -
-
9:41
-
- - - -
-
- {/* Notch */} -
-
-
- {/* Content */} -
-
-
- {renderLoginForm()} -
-
-
- {/* Home indicator */} -
-
-
-
- ); - - case 'tablet': - return ( -
- {/* Status bar */} -
-
9:41
-
- - - -
-
- {/* Content */} -
-
-
{renderLoginForm()}
-
-
- {/* Home button */} -
-
-
-
- ); - } - }; - - return ( -
-
- {(['desktop', 'mobile', 'tablet'] as DeviceType[]).map((device) => ( -
setActiveDevice(device)} - > - {device === 'desktop' && ( - - )} - {device === 'mobile' && ( - - )} - {device === 'tablet' && ( - - )} -

- {device.charAt(0).toUpperCase() + device.slice(1)} -

-
- ))} -
- -
- {renderDeviceFrame()} -
-
- ); -} diff --git a/frontend/plugins/tourism_ui/src/modules/tms/components/TmsFormFields.tsx b/frontend/plugins/tourism_ui/src/modules/tms/components/TmsFormFields.tsx index 2f01801ef1..77d39e87b1 100644 --- a/frontend/plugins/tourism_ui/src/modules/tms/components/TmsFormFields.tsx +++ b/frontend/plugins/tourism_ui/src/modules/tms/components/TmsFormFields.tsx @@ -1,7 +1,19 @@ +'use client'; + +import { useState } from 'react'; import { Control, useFieldArray } from 'react-hook-form'; -import { Button, Form, Input, Select, Upload } from 'erxes-ui'; +import { Button, Form, Input, Select, Upload, ColorPicker } from 'erxes-ui'; import { TmsFormType } from '@/tms/constants/formSchema'; -import { IconUpload, IconPlus, IconTrash } from '@tabler/icons-react'; +import { + IconUpload, + IconPlus, + IconTrash, + IconLoader2, +} from '@tabler/icons-react'; +import PaymentIcon, { paymentIconOptions } from '@/tms/components/PaymentIcon'; +import { SelectMember } from 'ui-modules'; +import { useQuery } from '@apollo/client'; +import { PAYMENT_LIST } from '@/tms/graphql/queries'; export const TourName = ({ control }: { control: Control }) => { return ( @@ -11,10 +23,16 @@ export const TourName = ({ control }: { control: Control }) => { render={({ field }) => ( - Tour Name * + Name * - + { + field.onChange(e); + }} + /> @@ -31,20 +49,17 @@ export const SelectColor = ({ control }: { control: Control }) => { render={({ field }) => ( - Main color * + Main color * -
- -
+ { + field.onChange(value); + }} + className="w-24" + />
@@ -54,6 +69,8 @@ export const SelectColor = ({ control }: { control: Control }) => { }; export const LogoField = ({ control }: { control: Control }) => { + const [isLoading, setIsLoading] = useState(false); + return ( }) => { { if ('url' in fileInfo) { @@ -74,28 +90,55 @@ export const LogoField = ({ control }: { control: Control }) => { } }} > -
- - - - - -

- Max size: 15MB, File type: PNG -

-
- - {field.value && ( - - )} -
+
+ ) : ( + <> + setIsLoading(true)} + onAllUploadsComplete={() => setIsLoading(false)} + /> + + + +

+ Max size: 15MB, File type: PNG +

+
+ + )} @@ -109,6 +152,8 @@ export const FavIconField = ({ }: { control: Control; }) => { + const [isLoading, setIsLoading] = useState(false); + return ( - -
- - - - - -

- Max size: 15MB, File type: PNG -

-
- - {field.value && ( - - )} -
+
+ ) : ( + <> + setIsLoading(true)} + onAllUploadsComplete={() => setIsLoading(false)} + /> + + + +

+ Max size: 15MB, File type: PNG +

+
+ + )} @@ -161,14 +232,7 @@ export const FavIconField = ({ ); }; -const TestGeneralManager = [ - { label: 'Bold Bold', value: '1' }, - { label: 'Bat Bat', value: '2' }, - { label: 'Toroo Toroo', value: '3' }, - { label: 'Temuulen Temuulen', value: '4' }, -]; - -export const GeneralManeger = ({ +export const GeneralManager = ({ control, }: { control: Control; @@ -176,54 +240,22 @@ export const GeneralManeger = ({ return ( ( - General maneger + General Managers - General maneger can be shown on the top of the post also in the list + General manager can be shown on the top of the post also in the list view - + +
+ +
+
)} @@ -231,64 +263,25 @@ export const GeneralManeger = ({ ); }; -const TestManegers = [ - { label: 'Bold Bold', value: '1' }, - { label: 'Bat Bat', value: '2' }, - { label: 'Toroo Toroo', value: '3' }, - { label: 'Temuulen Temuulen', value: '4' }, -]; - -export const Maneger = ({ control }: { control: Control }) => { +export const Manager = ({ control }: { control: Control }) => { return ( ( - Manegers + Managers - Maneger can be shown on the top of the post also in the list view + Manager can be shown on the top of the post also in the list view - + +
+ +
+
)} @@ -296,13 +289,11 @@ export const Maneger = ({ control }: { control: Control }) => { ); }; -const TestPayments = [ - { value: '1', label: 'Visa' }, - { value: '2', label: 'Qpay' }, - { value: '3', label: 'Mastercard' }, -]; - export const Payments = ({ control }: { control: Control }) => { + const { data, loading } = useQuery(PAYMENT_LIST); + const payments = data?.payments ?? []; + const isEmpty = !loading && payments.length === 0; + return ( }) => { Select payments that you want to use - -
- + - {'Select payments'} - - } - > - - { - TestPayments.find( - (status) => status.value === field.value?.[0], - )?.label - } - - + placeholder={loading ? 'Loading...' : 'Select payments'} + /> - - - - {TestPayments.map((status) => ( - - {status.label} + + {isEmpty ? ( + + the payment list is empty - ))} - - - - - + ) : ( + payments.map((payment: any) => ( + + {payment.name} + + )) + )} + + +
@@ -374,7 +349,7 @@ export const Token = ({ control }: { control: Control }) => { control={control} name="token" render={({ field }) => ( - + Erxes app token What is erxes app token ? @@ -392,14 +367,6 @@ export const OtherPayments = ({ }: { control: Control; }) => { - // Define Icon options - const Icon = [ - { value: 'visa', label: 'Visa' }, - { value: 'mastercard', label: 'Mastercard' }, - { value: 'qpay', label: 'QPay' }, - { value: 'socialpay', label: 'SocialPay' }, - ]; - const { fields, append, remove } = useFieldArray({ control, name: 'otherPayments', @@ -410,8 +377,8 @@ export const OtherPayments = ({ }; return ( -
-
+
+

Other Payments

@@ -425,10 +392,10 @@ export const OtherPayments = ({

-
+
+
+
- - - - ); - case 3: - return ( - <> + {/* Step 2 */} +
+
+ + +
+
+ + {/* Step 3 */} +
+
- - ); - default: - return null; - } +
+
+
+ ); }; const handleNext = async () => { if (currentStep === 1) { - const result = await form.trigger(['name', 'color', 'logo']); - if (result) setCurrentStep(2); + const result = await form.trigger(['name', 'color']); + if (result) { + setCurrentStep(2); + } } else if (currentStep === 2) { setCurrentStep(3); } @@ -82,73 +143,94 @@ export const TmsInformationFields = ({ if (onOpenChange) onOpenChange(false); } - function handleSave() { - form.trigger(['payment', 'token']).then((isValid) => { - if (isValid && onSubmit) { - onSubmit(form.getValues()); - } - }); + async function handleSave() { + const isValid = await form.trigger(['name', 'color']); + + if (!isValid || !onSubmit) { + return; + } + + onSubmit(form.getValues()); } return ( -
-
-
-
-
-

- STEP {currentStep} -

-
-

- {currentStep === 1 - ? 'General information' - : currentStep === 2 - ? 'Permission' - : 'Payments'} +

+
+
+
+

+ STEP {currentStep}

-
- {[1, 2, 3].map((step) => ( -
- ))} -
-

+

{currentStep === 1 - ? 'Set up your TMS information' + ? 'General information' : currentStep === 2 - ? 'Setup your permission' - : 'Setup your payments'} + ? 'Permission' + : 'Payments'}

-
+
+ {[1, 2, 3].map((step) => ( +
+ ))} +
+

+ {currentStep === 1 + ? 'Set up your TMS information' + : currentStep === 2 + ? 'Setup your permission' + : 'Setup your payments'} +

+
+
+
{renderStepContent()}
-
+
{currentStep === 1 ? ( - ) : ( - )} {currentStep < 3 ? ( - + ) : ( - + )}
diff --git a/frontend/plugins/tourism_ui/src/modules/tms/constants/formSchema.ts b/frontend/plugins/tourism_ui/src/modules/tms/constants/formSchema.ts index 610bcba467..02468dfa65 100644 --- a/frontend/plugins/tourism_ui/src/modules/tms/constants/formSchema.ts +++ b/frontend/plugins/tourism_ui/src/modules/tms/constants/formSchema.ts @@ -1,24 +1,25 @@ import { z } from 'zod'; export const TmsFormSchema = z.object({ - name: z.string().min(1, 'Tour name is required'), + name: z.string().min(1, 'Name is required'), color: z.string().min(1, 'Color is required'), - logo: z.string().optional(), - favIcon: z.string().optional(), - generalManeger: z.string().optional(), - manegers: z.array(z.string()).optional(), - payment: z.string().optional(), - token: z.string().optional(), + logo: z.string().optional().default(''), + favIcon: z.string().optional().default(''), + generalManager: z.array(z.string()).optional().default([]), + managers: z.array(z.string()).optional().default([]), + payment: z.string().optional().default(''), + token: z.string().optional().default(''), otherPayments: z .array( z.object({ - type: z.string().optional(), - title: z.string().optional(), - icon: z.string().optional(), + type: z.string().min(1, 'Type is required'), + title: z.string().min(1, 'Title is required'), + icon: z.string().min(1, 'Icon is required'), config: z.string().optional(), }), ) - .optional(), + .optional() + .default([]), }); export type TmsFormType = z.infer; diff --git a/frontend/plugins/tourism_ui/src/modules/tms/graphql/mutation.ts b/frontend/plugins/tourism_ui/src/modules/tms/graphql/mutation.ts index c6c5ffb8b7..91efacb913 100644 --- a/frontend/plugins/tourism_ui/src/modules/tms/graphql/mutation.ts +++ b/frontend/plugins/tourism_ui/src/modules/tms/graphql/mutation.ts @@ -1,61 +1,67 @@ import { gql } from '@apollo/client'; -export const CREATE_BRANCH_BRANCH = gql` - mutation bmsBranchAdd( +export const CREATE_BRANCH = gql` + mutation BmsBranchAdd( $name: String $description: String - $erxesAppToken: String - $user1Ids: [String] - $user2Ids: [String] + $generalManagerIds: [String] + $managerIds: [String] $paymentIds: [String] $paymentTypes: [JSON] - $uiOptions: JSON + $departmentId: String + $token: String + $erxesAppToken: String $permissionConfig: JSON + $uiOptions: JSON ) { bmsBranchAdd( name: $name description: $description - erxesAppToken: $erxesAppToken - user1Ids: $user1Ids - user2Ids: $user2Ids + generalManagerIds: $generalManagerIds + managerIds: $managerIds paymentIds: $paymentIds paymentTypes: $paymentTypes - uiOptions: $uiOptions + departmentId: $departmentId + token: $token + erxesAppToken: $erxesAppToken permissionConfig: $permissionConfig + uiOptions: $uiOptions ) { _id - name - description createdAt - token - erxesAppToken - user1Ids - user2Ids - paymentIds - paymentTypes + userId user { _id + username + email details { avatar fullName - __typename + shortName } - __typename } - uiOptions + name + description + generalManagerIds + managerIds + paymentIds + paymentTypes + departmentId + token + erxesAppToken permissionConfig - __typename + uiOptions } } `; -export const EDIT_BRANCH_LIST = gql` +export const EDIT_BRANCH = gql` mutation BmsBranchEdit( $id: String $name: String $description: String - $user1Ids: [String] - $user2Ids: [String] + $generalManagerIds: [String] + $managerIds: [String] $paymentIds: [String] $paymentTypes: [JSON] $departmentId: String @@ -68,8 +74,8 @@ export const EDIT_BRANCH_LIST = gql` _id: $id name: $name description: $description - user1Ids: $user1Ids - user2Ids: $user2Ids + generalManagerIds: $generalManagerIds + managerIds: $managerIds paymentIds: $paymentIds paymentTypes: $paymentTypes departmentId: $departmentId @@ -83,17 +89,18 @@ export const EDIT_BRANCH_LIST = gql` userId user { _id + username + email details { avatar fullName - __typename + shortName } - __typename } name description - user1Ids - user2Ids + generalManagerIds + managerIds paymentIds paymentTypes departmentId diff --git a/frontend/plugins/tourism_ui/src/modules/tms/graphql/queries.ts b/frontend/plugins/tourism_ui/src/modules/tms/graphql/queries.ts index 345e93a400..238b4732ea 100644 --- a/frontend/plugins/tourism_ui/src/modules/tms/graphql/queries.ts +++ b/frontend/plugins/tourism_ui/src/modules/tms/graphql/queries.ts @@ -1,32 +1,97 @@ import { gql } from '@apollo/client'; export const GET_BRANCH_LIST = gql` - query bmsBranchList($sortField: String, $sortDirection: Int) { - bmsBranchList(sortField: $sortField, sortDirection: $sortDirection) { + query BmsBranchList( + $limit: Int + $cursor: String + $cursorMode: CURSOR_MODE + $direction: CURSOR_DIRECTION + $orderBy: JSON + ) { + bmsBranchList( + limit: $limit + cursor: $cursor + cursorMode: $cursorMode + direction: $direction + orderBy: $orderBy + ) { + list { + _id + createdAt + userId + user { + _id + username + email + details { + avatar + fullName + shortName + } + } + name + description + generalManagerIds + managerIds + paymentIds + paymentTypes + departmentId + token + erxesAppToken + permissionConfig + uiOptions + } + totalCount + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + } + } +`; + +export const BRANCH_LIST_DETAIL = gql` + query BmsBranchDetail($id: String!) { + bmsBranchDetail(_id: $id) { _id - name - description createdAt - token - erxesAppToken - user1Ids - user2Ids - paymentIds - paymentTypes + userId user { _id + username + email details { avatar fullName - __typename + shortName } - __typename } - uiOptions + name + description + generalManagerIds + managerIds + paymentIds + paymentTypes + departmentId + token + erxesAppToken permissionConfig - __typename + uiOptions } } `; - +export const PAYMENT_LIST = gql` + query Payments($status: String, $kind: String) { + payments(status: $status, kind: $kind) { + _id + name + kind + status + config + createdAt + } + } +`; diff --git a/frontend/plugins/tourism_ui/src/modules/tms/hooks/BranchDetail.ts b/frontend/plugins/tourism_ui/src/modules/tms/hooks/BranchDetail.ts new file mode 100644 index 0000000000..3d0571c746 --- /dev/null +++ b/frontend/plugins/tourism_ui/src/modules/tms/hooks/BranchDetail.ts @@ -0,0 +1,26 @@ +import { useQuery } from '@apollo/client'; +import { BRANCH_LIST_DETAIL } from '../graphql/queries'; +import { IBranch } from '../types/branch'; + +interface BranchDetailResponse { + bmsBranchDetail: IBranch; +} + +interface BranchDetailVariables { + id: string; +} + +export const useBranchDetail = (variables: BranchDetailVariables) => { + const { data, loading, error } = useQuery< + BranchDetailResponse, + BranchDetailVariables + >(BRANCH_LIST_DETAIL, { + variables, + skip: !variables.id, + fetchPolicy: 'cache-and-network', + }); + + const branchDetail = data?.bmsBranchDetail; + + return { branchDetail, loading, error }; +}; diff --git a/frontend/plugins/tourism_ui/src/modules/tms/hooks/BranchDuplicate.ts b/frontend/plugins/tourism_ui/src/modules/tms/hooks/BranchDuplicate.ts new file mode 100644 index 0000000000..60e3bb0772 --- /dev/null +++ b/frontend/plugins/tourism_ui/src/modules/tms/hooks/BranchDuplicate.ts @@ -0,0 +1,85 @@ +import { useMutation } from '@apollo/client'; +import { CREATE_BRANCH } from '@/tms/graphql/mutation'; +import { IBranch } from '../types/branch'; +import { toast } from 'erxes-ui'; + +interface DuplicateBranchVariables { + name: string; + description?: string; + generalManagerIds?: string[]; + managerIds?: string[]; + paymentIds?: string[]; + paymentTypes?: any[]; + erxesAppToken?: string; + permissionConfig?: any[]; + uiOptions?: Record; +} + +interface UseBranchDuplicateOptions { + onSuccess?: () => void; + onError?: (error: any) => void; +} + +export const useBranchDuplicate = (options?: UseBranchDuplicateOptions) => { + const [createBranch, { loading }] = useMutation(CREATE_BRANCH); + + const duplicateBranch = async (variables: { + variables: DuplicateBranchVariables; + onCompleted?: () => void; + }) => { + try { + await createBranch({ + variables: variables.variables, + onCompleted: () => { + variables.onCompleted?.(); + options?.onSuccess?.(); + }, + }); + } catch (error) { + options?.onError?.(error); + throw error; + } + }; + + const handleDuplicateBranch = async ( + branch: IBranch, + refetch?: () => Promise, + ) => { + try { + await duplicateBranch({ + variables: { + name: `${branch.name} (Copy)`, + description: branch.description, + generalManagerIds: branch.generalManagerIds || [], + managerIds: branch.managerIds || [], + paymentIds: branch.paymentIds || [], + paymentTypes: branch.paymentTypes || [], + erxesAppToken: branch.erxesAppToken, + permissionConfig: branch.permissionConfig || [], + uiOptions: branch.uiOptions || {}, + }, + onCompleted: async () => { + toast({ + title: 'Branch duplicated successfully', + }); + if (refetch) { + await refetch(); + } + }, + }); + } catch (error) { + toast({ + title: 'Failed to duplicate branch', + description: error?.message || 'Unknown error occurred', + variant: 'destructive', + }); + throw error; + } + }; + + return { + duplicateBranch, + handleDuplicateBranch, + loading, + }; +}; diff --git a/frontend/plugins/tourism_ui/src/modules/tms/hooks/BranchEdit.ts b/frontend/plugins/tourism_ui/src/modules/tms/hooks/BranchEdit.ts index 26a4e82332..f9719d953c 100644 --- a/frontend/plugins/tourism_ui/src/modules/tms/hooks/BranchEdit.ts +++ b/frontend/plugins/tourism_ui/src/modules/tms/hooks/BranchEdit.ts @@ -1,16 +1,115 @@ -import { useQuery } from '@apollo/client'; -import { EDIT_BRANCH_LIST } from '../graphql/mutation'; -import { IBranch } from '../types/branch'; +import { useState } from 'react'; +import { useMutation } from '@apollo/client'; +import { EDIT_BRANCH } from '@/tms/graphql/mutation'; +import { toast } from 'erxes-ui'; -interface BranchListResponse { - bmsBranchList: IBranch[]; +interface UpdateBranchVariables { + id: string; + name?: string; + description?: string; + generalManagerIds?: string[]; + managerIds?: string[]; + paymentIds?: string[]; + paymentTypes?: any[]; + erxesAppToken?: string; + permissionConfig?: any; + uiOptions?: any; } -export const useBranchEdit = () => { - const { data, loading, error } = - useQuery(EDIT_BRANCH_LIST); +interface UseBranchEditOptions { + onSuccess?: () => void; + onError?: (error: any) => void; +} + +export const useBranchEdit = (options?: UseBranchEditOptions) => { + const [editingBranch, setEditingBranch] = useState(null); + const [updateBranch, { loading }] = useMutation(EDIT_BRANCH); + + const updateBranchById = async (variables: UpdateBranchVariables) => { + try { + const result = await updateBranch({ + variables, + }); + + if (result.data) { + options?.onSuccess?.(); + return result.data; + } + return null; + } catch (error) { + options?.onError?.(error); + throw error; + } + }; + + const editBranch = async ({ + variables, + onError, + onCompleted, + }: { + variables: UpdateBranchVariables; + onError?: (error: any) => void; + onCompleted?: () => void; + }) => { + try { + const result = await updateBranch({ + variables, + }); + + if (result.data) { + onCompleted?.(); + options?.onSuccess?.(); + return result.data; + } + return null; + } catch (error) { + onError?.(error); + options?.onError?.(error); + throw error; + } + }; + + const handleEditBranch = (branchId: string) => { + setEditingBranch(branchId); + }; + + const handleUpdateBranch = async ( + branchData: UpdateBranchVariables, + refetch?: () => Promise, + ) => { + try { + await updateBranchById(branchData); + + toast({ + title: 'Branch updated successfully', + }); + + if (refetch) { + await refetch(); + } + + setEditingBranch(null); + } catch (error) { + toast({ + title: 'Failed to update branch', + description: error?.message || 'Unknown error occurred', + variant: 'destructive', + }); + throw error; + } + }; - const list = data?.bmsBranchList || []; + const closeEditDialog = () => { + setEditingBranch(null); + }; - return { list, loading, error }; + return { + editingBranch, + updateBranchById, + editBranch, + handleEditBranch, + handleUpdateBranch, + closeEditDialog, + loading, + }; }; diff --git a/frontend/plugins/tourism_ui/src/modules/tms/hooks/BranchList.ts b/frontend/plugins/tourism_ui/src/modules/tms/hooks/BranchList.ts index fc48398103..dc403cc713 100644 --- a/frontend/plugins/tourism_ui/src/modules/tms/hooks/BranchList.ts +++ b/frontend/plugins/tourism_ui/src/modules/tms/hooks/BranchList.ts @@ -2,15 +2,54 @@ import { useQuery } from '@apollo/client'; import { GET_BRANCH_LIST } from '../graphql/queries'; import { IBranch } from '../types/branch'; +interface PageInfo { + hasNextPage: boolean; + hasPreviousPage: boolean; + startCursor: string | null; + endCursor: string | null; +} + interface BranchListResponse { - bmsBranchList: IBranch[]; + bmsBranchList: { + list: IBranch[]; + totalCount: number; + pageInfo: PageInfo; + }; +} + +interface UseBranchListParams { + limit?: number; + cursor?: string; + cursorMode?: 'AFTER' | 'BEFORE'; + direction?: 'FORWARD' | 'BACKWARD'; + orderBy?: Record; } -export const useBranchList = () => { - const { data, loading, error } = - useQuery(GET_BRANCH_LIST); +export const useBranchList = ({ + limit = 10, + cursor, + cursorMode, + direction, + orderBy = { createdAt: -1 }, +}: UseBranchListParams = {}) => { + const { data, loading, error, refetch } = useQuery( + GET_BRANCH_LIST, + { + variables: { + limit, + cursor, + cursorMode, + direction, + orderBy, + }, + fetchPolicy: 'network-only', + notifyOnNetworkStatusChange: true, + }, + ); - const list = data?.bmsBranchList || []; + const list = data?.bmsBranchList?.list || []; + const totalCount = data?.bmsBranchList?.totalCount || 0; + const pageInfo = data?.bmsBranchList?.pageInfo; - return { list, loading, error }; + return { list, totalCount, pageInfo, loading, error, refetch }; }; diff --git a/frontend/plugins/tourism_ui/src/modules/tms/hooks/BranchRemove.ts b/frontend/plugins/tourism_ui/src/modules/tms/hooks/BranchRemove.ts index ec4fd4f8af..a8a546c613 100644 --- a/frontend/plugins/tourism_ui/src/modules/tms/hooks/BranchRemove.ts +++ b/frontend/plugins/tourism_ui/src/modules/tms/hooks/BranchRemove.ts @@ -1,30 +1,63 @@ import { useMutation } from '@apollo/client'; -import { REMOVE_BRANCH } from '../graphql/mutation'; -import { GET_BRANCH_LIST } from '../graphql/queries'; -import { IBranchRemoveResponse, IBranchRemoveVariables } from '../types/branch'; +import { REMOVE_BRANCH } from '@/tms/graphql/mutation'; +import { toast } from 'erxes-ui'; -export const useBranchRemove = () => { - const [removeBranch, { loading, error }] = useMutation< - IBranchRemoveResponse, - IBranchRemoveVariables - >(REMOVE_BRANCH, { - refetchQueries: [{ query: GET_BRANCH_LIST }], - onError: (error) => { - console.error('Error removing branch:', error); - }, - }); +interface UseBranchRemoveOptions { + onSuccess?: () => void; + onError?: (error: any) => void; +} - const removeBranchById = async (branchId: string) => { +export const useBranchRemove = (options?: UseBranchRemoveOptions) => { + const [deleteBranch, { loading }] = useMutation(REMOVE_BRANCH); + + const removeBranchById = async (branchId: string): Promise => { try { - const response = await removeBranch({ + const result = await deleteBranch({ variables: { id: branchId }, }); - return response.data?.bmsBranchRemove || false; - } catch (error) { + if (result.data) { + options?.onSuccess?.(); + return true; + } return false; + } catch (error) { + options?.onError?.(error); + throw error; } }; - return { removeBranchById, loading, error }; + const handleDeleteBranch = async ( + branchId: string, + refetch?: () => Promise, + ) => { + try { + const success = await removeBranchById(branchId); + + if (success) { + toast({ + title: 'Branch deleted successfully', + }); + if (refetch) { + await refetch(); + } + } else { + toast({ + title: 'Failed to delete branch', + }); + } + } catch (error) { + toast({ + title: 'Failed to delete branch', + description: `${error.message}`, + }); + throw error; + } + }; + + return { + removeBranchById, + handleDeleteBranch, + loading, + }; }; diff --git a/frontend/plugins/tourism_ui/src/modules/tms/hooks/CreateBranch.ts b/frontend/plugins/tourism_ui/src/modules/tms/hooks/CreateBranch.ts index 90ab79863f..efd75d5b49 100644 --- a/frontend/plugins/tourism_ui/src/modules/tms/hooks/CreateBranch.ts +++ b/frontend/plugins/tourism_ui/src/modules/tms/hooks/CreateBranch.ts @@ -1,5 +1,6 @@ import { useMutation } from '@apollo/client'; -import { CREATE_BRANCH_BRANCH } from '../graphql/mutation'; +import { CREATE_BRANCH } from '../graphql/mutation'; +import { GET_BRANCH_LIST } from '../graphql/queries'; import { IBranch } from '../types/branch'; interface CreateBranchResponse { @@ -9,22 +10,44 @@ interface CreateBranchResponse { export interface ICreateBranchVariables { name: string; description?: string; - user1Ids?: string[]; - user2Ids?: string[]; + generalManagerIds?: string[]; + managerIds?: string[]; paymentIds?: string[]; - paymentTypes?: any[]; departmentId?: string; token?: string; erxesAppToken?: string; - permissionConfig?: any; - uiOptions?: any; + permissionConfig?: { + _id?: string; + type: string; + title: string; + icon: string; + config?: string; + }[]; + uiOptions?: { + logo?: string; + favIcon?: string; + colors?: { + primary?: string; + }; + }; } export const useCreateBranch = () => { const [createBranchMutation, { loading, error }] = useMutation< CreateBranchResponse, ICreateBranchVariables - >(CREATE_BRANCH_BRANCH); + >(CREATE_BRANCH, { + refetchQueries: [ + { + query: GET_BRANCH_LIST, + variables: { + limit: 10, + orderBy: { createdAt: -1 }, + }, + }, + ], + awaitRefetchQueries: true, + }); const createBranch = (options: { variables: ICreateBranchVariables; @@ -34,5 +57,9 @@ export const useCreateBranch = () => { return createBranchMutation(options); }; - return { createBranch, loading, error }; + return { + createBranch, + loading, + error, + }; }; diff --git a/frontend/plugins/tourism_ui/src/modules/tms/hooks/useBranchSubmit.ts b/frontend/plugins/tourism_ui/src/modules/tms/hooks/useBranchSubmit.ts new file mode 100644 index 0000000000..9759a0f7d2 --- /dev/null +++ b/frontend/plugins/tourism_ui/src/modules/tms/hooks/useBranchSubmit.ts @@ -0,0 +1,144 @@ +import { useToast } from 'erxes-ui'; +import { ApolloError } from '@apollo/client'; +import { UseFormReturn } from 'react-hook-form'; +import { useSetAtom } from 'jotai'; +import { useCreateBranch } from '@/tms/hooks/CreateBranch'; +import { useBranchEdit } from '@/tms/hooks/BranchEdit'; +import { TmsFormType } from '@/tms/constants/formSchema'; +import { resetFormAtom, DEFAULT_TMS_FORM } from '@/tms/atoms/formAtoms'; +import { currentStepAtom } from '@/tms/states/tmsInformationFieldsAtoms'; + +interface PermissionConfig { + type: string; + title: string; + icon: string; + config?: string; +} + +interface UseBranchSubmitParams { + isEditMode: boolean; + branchId?: string; + form: UseFormReturn; + refetch?: () => Promise; + onOpenChange?: (open: boolean) => void; + onSuccess?: () => void; +} + +export function useBranchSubmit({ + isEditMode, + branchId, + form, + refetch, + onOpenChange, + onSuccess, +}: UseBranchSubmitParams) { + const { toast } = useToast(); + const { createBranch, loading: createLoading } = useCreateBranch(); + const { editBranch, loading: editLoading } = useBranchEdit(); + const resetForm = useSetAtom(resetFormAtom); + const setCurrentStep = useSetAtom(currentStepAtom); + + const isLoading = isEditMode ? editLoading : createLoading; + + const handleRefetch = async () => { + if (refetch) { + try { + await refetch(); + } catch (error) { + toast({ + title: 'Warning', + description: error instanceof Error ? error.message : String(error), + variant: 'destructive', + }); + } + } + }; + + const transformFormData = (data: TmsFormType) => { + const permissionConfig = + data.otherPayments?.map((payment: PermissionConfig) => ({ + type: payment.type, + title: payment.title, + icon: payment.icon, + config: payment.config, + })) || []; + + return { + name: data.name, + generalManagerIds: data.generalManager || [], + managerIds: data.managers || [], + paymentIds: data.payment ? [data.payment] : [], + permissionConfig, + erxesAppToken: data.token, + uiOptions: { + logo: data.logo || '', + favIcon: data.favIcon || '', + colors: { + primary: data.color, + }, + }, + }; + }; + + const handleEditBranch = (variables: any) => { + editBranch({ + variables: { + id: branchId, + ...variables, + }, + onError: (e: ApolloError) => { + toast({ + title: 'Error', + description: e.message, + variant: 'destructive', + }); + }, + onCompleted: async () => { + toast({ + title: 'Success', + description: 'Branch updated successfully', + }); + onOpenChange?.(false); + onSuccess?.(); + await handleRefetch(); + }, + }); + }; + + const handleCreateBranch = (variables: any) => { + createBranch({ + variables, + onError: (e: ApolloError) => { + toast({ + title: 'Error', + description: e.message, + variant: 'destructive', + }); + }, + onCompleted: async () => { + toast({ + title: 'Success', + description: 'Branch created successfully', + }); + resetForm(); + form.reset(DEFAULT_TMS_FORM); + setCurrentStep(0); + onOpenChange?.(false); + onSuccess?.(); + await handleRefetch(); + }, + }); + }; + + const handleSubmit = (data: TmsFormType) => { + const variables = transformFormData(data); + + if (isEditMode) { + handleEditBranch(variables); + } else { + handleCreateBranch(variables); + } + }; + + return { handleSubmit, isLoading }; +} diff --git a/frontend/plugins/tourism_ui/src/modules/tms/states/tmsInformationFieldsAtoms.ts b/frontend/plugins/tourism_ui/src/modules/tms/states/tmsInformationFieldsAtoms.ts new file mode 100644 index 0000000000..d236ab84c2 --- /dev/null +++ b/frontend/plugins/tourism_ui/src/modules/tms/states/tmsInformationFieldsAtoms.ts @@ -0,0 +1,4 @@ +import { atom } from 'jotai'; + +export const currentStepAtom = atom(1); + diff --git a/frontend/plugins/tourism_ui/src/modules/tms/types/branch.ts b/frontend/plugins/tourism_ui/src/modules/tms/types/branch.ts index fa7478ffb6..0e69e32d44 100644 --- a/frontend/plugins/tourism_ui/src/modules/tms/types/branch.ts +++ b/frontend/plugins/tourism_ui/src/modules/tms/types/branch.ts @@ -17,6 +17,8 @@ export interface IBranch { erxesAppToken: string; user1Ids: string[]; user2Ids: string[]; + generalManagerIds: string[]; + managerIds: string[]; paymentIds: string[]; paymentTypes: string[]; user: IBranchUser; diff --git a/frontend/plugins/tourism_ui/src/pages/tms/PreviewPage.tsx b/frontend/plugins/tourism_ui/src/pages/tms/PreviewPage.tsx new file mode 100644 index 0000000000..6ccc931e08 --- /dev/null +++ b/frontend/plugins/tourism_ui/src/pages/tms/PreviewPage.tsx @@ -0,0 +1,111 @@ +import { useEffect, useLayoutEffect, useState } from 'react'; +import { hexToHsl, Input, Select, readImage } from 'erxes-ui'; +import { useAtom } from 'jotai'; +import { tmsFormAtom } from '~/modules/tms/atoms/formAtoms'; + +export const PreviewPage = () => { + const [formData] = useAtom(tmsFormAtom); + const [logoUrl, setLogoUrl] = useState(''); + + useLayoutEffect(() => { + if (formData.color && hexToHsl(formData.color)) { + document.documentElement.style.setProperty( + '--primary', + hexToHsl(formData.color) || '', + ); + } + }, [formData.color]); + + const tourName = formData.name || 'Tour Management System'; + + useEffect(() => { + if (formData.logo) { + setLogoUrl(readImage(formData.logo)); + } else { + setLogoUrl('https://placehold.co/150x150'); + } + }, [formData.logo]); + + const themeColor = formData.color || '#4F46E5'; + + return ( +
+
+
+
+ Company Logo +
+

+ Sign in to your account +

+

+ Enter your email and password below to access your account. +

+
+ + +
+ + +
+
+
+ +

+ Forgot password? +

+
+ +
+ +
+
+
+
+ ); +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d29622de0d..b2b8ae18f0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -927,24 +927,6 @@ importers: specifier: ^7.0.4 version: 7.1.5(@types/node@18.16.9)(jiti@1.21.7)(less@4.1.3)(sass@1.86.3)(stylus@0.64.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1) - backend/plugins/pos_api: - dependencies: - aws-sdk: - specifier: ^2.1692.0 - version: 2.1692.0 - debug: - specifier: ^4.4.0 - version: 4.4.0(supports-color@5.5.0) - erxes-api-shared: - specifier: workspace:^ - version: link:../../erxes-api-shared - fbgraph: - specifier: ^1.4.4 - version: 1.4.4 - moment-timezone: - specifier: ^0.5.48 - version: 0.5.48 - backend/plugins/posclient_api: dependencies: aws-sdk: