diff --git a/backend/src/db/api/businesses.js b/backend/src/db/api/businesses.js index cac5747..b2b0110 100644 --- a/backend/src/db/api/businesses.js +++ b/backend/src/db/api/businesses.js @@ -23,7 +23,7 @@ module.exports = class BusinessesDBApi { const limit = filter.limit || 0; let offset = 0; let where = {}; - const currentPage = +filter.page; + const currentPage = +filter.page || 0; offset = currentPage * limit; const currentUser = options?.currentUser; @@ -42,11 +42,67 @@ module.exports = class BusinessesDBApi { where.is_active = true; } - let include = [{ model: db.users, as: 'owner_user' }]; + let include = [ + { model: db.users, as: 'owner_user' }, + { + model: db.business_photos, + as: 'business_photos_business', + include: [{ + model: db.file, + as: 'photos' + }] + } + ]; if (filter) { if (filter.id) where.id = Utils.uuid(filter.id); - if (filter.name) where.name = { [Op.iLike]: `%${filter.name}%` }; + + const searchConditions = []; + + if (filter.name) { + const terms = filter.name.split(' ').filter(t => t.length > 0); + if (terms.length > 0) { + const termConditions = terms.map(term => ({ + [Op.or]: [ + { name: { [Op.iLike]: `%${term}%` } }, + { description: { [Op.iLike]: `%${term}%` } }, + { address: { [Op.iLike]: `%${term}%` } }, + { city: { [Op.iLike]: `%${term}%` } }, + { zip: { [Op.iLike]: `%${term}%` } } + ] + })); + searchConditions.push({ [Op.and]: termConditions }); + } + } + + if (filter.city || filter.zip) { + const location = filter.city || filter.zip; + const terms = location.split(' ').filter(t => t.length > 0); + if (terms.length > 0) { + const termConditions = terms.map(term => ({ + [Op.or]: [ + { city: { [Op.iLike]: `%${term}%` } }, + { zip: { [Op.iLike]: `%${term}%` } }, + { address: { [Op.iLike]: `%${term}%` } } + ] + })); + searchConditions.push({ [Op.and]: termConditions }); + } + } + + if (searchConditions.length > 0) { + where[Op.and] = searchConditions; + } + + if (filter.is_active) where.is_active = filter.is_active === 'true'; + + if (filter.category) { + include.push({ + model: db.business_categories, + as: 'business_categories_business', + where: { categoryId: filter.category } + }); + } } const queryOptions = { diff --git a/frontend/src/components/AsideMenuLayer.tsx b/frontend/src/components/AsideMenuLayer.tsx index 63d5823..6cec84d 100644 --- a/frontend/src/components/AsideMenuLayer.tsx +++ b/frontend/src/components/AsideMenuLayer.tsx @@ -1,11 +1,9 @@ import React from 'react' -import { mdiLogout, mdiClose } from '@mdi/js' -import BaseIcon from './BaseIcon' +import { mdiClose, mdiMagnify } from '@mdi/js' import AsideMenuList from './AsideMenuList' import { MenuAsideItem } from '../interfaces' import { useAppSelector } from '../stores/hooks' -import Link from 'next/link'; - +import BaseIcon from './BaseIcon' type Props = { menu: MenuAsideItem[] @@ -14,50 +12,50 @@ type Props = { } export default function AsideMenuLayer({ menu, className = '', ...props }: Props) { - const corners = useAppSelector((state) => state.style.corners); - const asideStyle = useAppSelector((state) => state.style.asideStyle) - const asideBrandStyle = useAppSelector((state) => state.style.asideBrandStyle) - const asideScrollbarsStyle = useAppSelector((state) => state.style.asideScrollbarsStyle) - const darkMode = useAppSelector((state) => state.style.darkMode) + const { asideStyle, asideBrandStyle, asideScrollbarsStyle } = useAppSelector( + (state) => state.style + ) const handleAsideLgCloseClick = (e: React.MouseEvent) => { e.preventDefault() props.onAsideLgCloseClick() } - return ( ) -} +} \ No newline at end of file diff --git a/frontend/src/helpers/dataFormatter.js b/frontend/src/helpers/dataFormatter.js index d83f0b8..7e9ab70 100644 --- a/frontend/src/helpers/dataFormatter.js +++ b/frontend/src/helpers/dataFormatter.js @@ -1,221 +1,238 @@ import dayjs from 'dayjs'; import _ from 'lodash'; +export const filesFormatter = (arr) => { + if (!arr || !arr.length) return []; + return arr.map((item) => item); +}; + +export const imageFormatter = (arr) => { + if (!arr || !arr.length) return [] + return arr.map(item => ({ + publicUrl: item.publicUrl || '' + })) +}; + +export const oneImageFormatter = (arr) => { + if (!arr || !arr.length) return '' + return arr[0].publicUrl || '' +}; + +export const dateFormatter = (date) => { + if (!date) return '' + return dayjs(date).format('YYYY-MM-DD') +}; + +export const dateTimeFormatter = (date) => { + if (!date) return '' + return dayjs(date).format('YYYY-MM-DD HH:mm') +}; + +export const booleanFormatter = (val) => { + return val ? 'Yes' : 'No' +}; + +export const dataGridEditFormatter = (obj) => { + return _.transform(obj, (result, value, key) => { + if (_.isArray(value)) { + result[key] = _.map(value, 'id'); + } else if (_.isObject(value)) { + result[key] = value.id; + } else { + result[key] = value; + } + }); +}; + +export const usersManyListFormatter = (val) => { + if (!val || !val.length) return [] + return val.map((item) => item.firstName) +}; + +export const usersOneListFormatter = (val) => { + if (!val) return '' + return val.firstName +}; + +export const usersManyListFormatterEdit = (val) => { + if (!val || !val.length) return [] + return val.map((item) => { + return {id: item.id, label: item.firstName} + }); +}; + +export const usersOneListFormatterEdit = (val) => { + if (!val) return '' + return {label: val.firstName, id: val.id} +}; + +export const rolesManyListFormatter = (val) => { + if (!val || !val.length) return [] + return val.map((item) => item.name) +}; + +export const rolesOneListFormatter = (val) => { + if (!val) return '' + return val.name +}; + +export const rolesManyListFormatterEdit = (val) => { + if (!val || !val.length) return [] + return val.map((item) => { + return {id: item.id, label: item.name} + }); +}; + +export const rolesOneListFormatterEdit = (val) => { + if (!val) return '' + return {label: val.name, id: val.id} +}; + +export const permissionsManyListFormatter = (val) => { + if (!val || !val.length) return [] + return val.map((item) => item.name) +}; + +export const permissionsOneListFormatter = (val) => { + if (!val) return '' + return val.name +}; + +export const permissionsManyListFormatterEdit = (val) => { + if (!val || !val.length) return [] + return val.map((item) => { + return {id: item.id, label: item.name} + }); +}; + +export const permissionsOneListFormatterEdit = (val) => { + if (!val) return '' + return {label: val.name, id: val.id} +}; + +export const categoriesManyListFormatter = (val) => { + if (!val || !val.length) return [] + return val.map((item) => item.name) +}; + +export const categoriesOneListFormatter = (val) => { + if (!val) return '' + return val.name +}; + +export const categoriesManyListFormatterEdit = (val) => { + if (!val || !val.length) return [] + return val.map((item) => { + return {id: item.id, label: item.name} + }); +}; + +export const categoriesOneListFormatterEdit = (val) => { + if (!val) return '' + return {label: val.name, id: val.id} +}; + +export const businessesManyListFormatter = (val) => { + if (!val || !val.length) return [] + return val.map((item) => item.name) +}; + +export const businessesOneListFormatter = (val) => { + if (!val) return '' + return val.name +}; + +export const businessesManyListFormatterEdit = (val) => { + if (!val || !val.length) return [] + return val.map((item) => { + return {id: item.id, label: item.name} + }); +}; + +export const businessesOneListFormatterEdit = (val) => { + if (!val) return '' + return {label: val.name, id: val.id} +}; + +export const verification_submissionsManyListFormatter = (val) => { + if (!val || !val.length) return [] + return val.map((item) => item.notes) +}; + +export const verification_submissionsOneListFormatter = (val) => { + if (!val) return '' + return val.notes +}; + +export const verification_submissionsManyListFormatterEdit = (val) => { + if (!val || !val.length) return [] + return val.map((item) => { + return {id: item.id, label: item.notes} + }); +}; + +export const verification_submissionsOneListFormatterEdit = (val) => { + if (!val) return '' + return {label: val.notes, id: val.id} +}; + +export const leadsManyListFormatter = (val) => { + if (!val || !val.length) return [] + return val.map((item) => item.keyword) +}; + +export const leadsOneListFormatter = (val) => { + if (!val) return '' + return val.keyword +}; + +export const leadsManyListFormatterEdit = (val) => { + if (!val || !val.length) return [] + return val.map((item) => { + return {id: item.id, label: item.keyword} + }); +}; + +export const leadsOneListFormatterEdit = (val) => { + if (!val) return '' + return {label: val.keyword, id: val.id} +}; + +// Also keep the default export for compatibility export default { - filesFormatter(arr) { - if (!arr || !arr.length) return []; - return arr.map((item) => item); - }, - imageFormatter(arr) { - if (!arr || !arr.length) return [] - return arr.map(item => ({ - publicUrl: item.publicUrl || '' - })) - }, - oneImageFormatter(arr) { - if (!arr || !arr.length) return '' - return arr[0].publicUrl || '' - }, - dateFormatter(date) { - if (!date) return '' - return dayjs(date).format('YYYY-MM-DD') - }, - dateTimeFormatter(date) { - if (!date) return '' - return dayjs(date).format('YYYY-MM-DD HH:mm') - }, - booleanFormatter(val) { - return val ? 'Yes' : 'No' - }, - dataGridEditFormatter(obj) { - return _.transform(obj, (result, value, key) => { - if (_.isArray(value)) { - result[key] = _.map(value, 'id'); - } else if (_.isObject(value)) { - result[key] = value.id; - } else { - result[key] = value; - } - }); - }, - - - usersManyListFormatter(val) { - if (!val || !val.length) return [] - return val.map((item) => item.firstName) - }, - usersOneListFormatter(val) { - if (!val) return '' - return val.firstName - }, - usersManyListFormatterEdit(val) { - if (!val || !val.length) return [] - return val.map((item) => { - return {id: item.id, label: item.firstName} - }); - }, - usersOneListFormatterEdit(val) { - if (!val) return '' - return {label: val.firstName, id: val.id} - }, - - - - rolesManyListFormatter(val) { - if (!val || !val.length) return [] - return val.map((item) => item.name) - }, - rolesOneListFormatter(val) { - if (!val) return '' - return val.name - }, - rolesManyListFormatterEdit(val) { - if (!val || !val.length) return [] - return val.map((item) => { - return {id: item.id, label: item.name} - }); - }, - rolesOneListFormatterEdit(val) { - if (!val) return '' - return {label: val.name, id: val.id} - }, - - - - permissionsManyListFormatter(val) { - if (!val || !val.length) return [] - return val.map((item) => item.name) - }, - permissionsOneListFormatter(val) { - if (!val) return '' - return val.name - }, - permissionsManyListFormatterEdit(val) { - if (!val || !val.length) return [] - return val.map((item) => { - return {id: item.id, label: item.name} - }); - }, - permissionsOneListFormatterEdit(val) { - if (!val) return '' - return {label: val.name, id: val.id} - }, - - - - - - categoriesManyListFormatter(val) { - if (!val || !val.length) return [] - return val.map((item) => item.name) - }, - categoriesOneListFormatter(val) { - if (!val) return '' - return val.name - }, - categoriesManyListFormatterEdit(val) { - if (!val || !val.length) return [] - return val.map((item) => { - return {id: item.id, label: item.name} - }); - }, - categoriesOneListFormatterEdit(val) { - if (!val) return '' - return {label: val.name, id: val.id} - }, - - - - - - businessesManyListFormatter(val) { - if (!val || !val.length) return [] - return val.map((item) => item.name) - }, - businessesOneListFormatter(val) { - if (!val) return '' - return val.name - }, - businessesManyListFormatterEdit(val) { - if (!val || !val.length) return [] - return val.map((item) => { - return {id: item.id, label: item.name} - }); - }, - businessesOneListFormatterEdit(val) { - if (!val) return '' - return {label: val.name, id: val.id} - }, - - - - - - - - - - - - verification_submissionsManyListFormatter(val) { - if (!val || !val.length) return [] - return val.map((item) => item.notes) - }, - verification_submissionsOneListFormatter(val) { - if (!val) return '' - return val.notes - }, - verification_submissionsManyListFormatterEdit(val) { - if (!val || !val.length) return [] - return val.map((item) => { - return {id: item.id, label: item.notes} - }); - }, - verification_submissionsOneListFormatterEdit(val) { - if (!val) return '' - return {label: val.notes, id: val.id} - }, - - - - - - leadsManyListFormatter(val) { - if (!val || !val.length) return [] - return val.map((item) => item.keyword) - }, - leadsOneListFormatter(val) { - if (!val) return '' - return val.keyword - }, - leadsManyListFormatterEdit(val) { - if (!val || !val.length) return [] - return val.map((item) => { - return {id: item.id, label: item.keyword} - }); - }, - leadsOneListFormatterEdit(val) { - if (!val) return '' - return {label: val.keyword, id: val.id} - }, - - - - - - - - - - - - - - - - - - - - -} + filesFormatter, + imageFormatter, + oneImageFormatter, + dateFormatter, + dateTimeFormatter, + booleanFormatter, + dataGridEditFormatter, + usersManyListFormatter, + usersOneListFormatter, + usersManyListFormatterEdit, + usersOneListFormatterEdit, + rolesManyListFormatter, + rolesOneListFormatter, + rolesManyListFormatterEdit, + rolesOneListFormatterEdit, + permissionsManyListFormatter, + permissionsOneListFormatter, + permissionsManyListFormatterEdit, + permissionsOneListFormatterEdit, + categoriesManyListFormatter, + categoriesOneListFormatter, + categoriesManyListFormatterEdit, + categoriesOneListFormatterEdit, + businessesManyListFormatter, + businessesOneListFormatter, + businessesManyListFormatterEdit, + businessesOneListFormatterEdit, + verification_submissionsManyListFormatter, + verification_submissionsOneListFormatter, + verification_submissionsManyListFormatterEdit, + verification_submissionsOneListFormatterEdit, + leadsManyListFormatter, + leadsOneListFormatter, + leadsManyListFormatterEdit, + leadsOneListFormatterEdit, +}; \ No newline at end of file diff --git a/frontend/src/layouts/Guest.tsx b/frontend/src/layouts/Guest.tsx index e1c58c3..ea2a660 100644 --- a/frontend/src/layouts/Guest.tsx +++ b/frontend/src/layouts/Guest.tsx @@ -1,116 +1,176 @@ -import React, { ReactNode } from 'react'; -import Link from 'next/link'; -import { useRouter } from 'next/router'; -import { mdiShieldCheck, mdiMenu, mdiClose, mdiMagnify } from '@mdi/js'; -import { useAppSelector } from '../stores/hooks'; -import BaseIcon from '../components/BaseIcon'; +import React, { ReactNode, useState, useEffect } from 'react' +import Link from 'next/link' +import { mdiMenu, mdiClose, mdiLogin, mdiAccountPlus, mdiMagnify } from '@mdi/js' +import { useAppSelector } from '../stores/hooks' +import { useRouter } from 'next/router' +import BaseIcon from '../components/BaseIcon' type Props = { children: ReactNode } export default function LayoutGuest({ children }: Props) { - const darkMode = useAppSelector((state) => state.style.darkMode); - const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const { currentUser } = useAppSelector((state) => state.auth); - const [isMenuOpen, setIsMenuOpen] = React.useState(false); - const router = useRouter(); + const [isMenuOpen, setIsMenuOpen] = useState(false) + const [scrolled, setScrolled] = useState(false) + const { token } = useAppSelector((state) => state.auth) + const router = useRouter() + + useEffect(() => { + const handleScroll = () => { + setScrolled(window.scrollY > 20) + } + window.addEventListener('scroll', handleScroll) + return () => window.removeEventListener('scroll', handleScroll) + }, []) const navLinks = [ - { href: '/search', label: 'Find Services' }, - { href: '/register', label: 'List Business' }, - ]; + { label: 'Find Services', href: '/search' }, + { label: 'Categories', href: '/categories' }, + { label: 'Verify Business', href: '/register' }, + { label: 'Support', href: '/contact-form' }, + ] return ( -
- {/* Dynamic Header */} -
-
- -
- +
+ {/* Navigation */} +
+ {/* Mobile Menu */} {isMenuOpen && (
{navLinks.map((link) => ( - setIsMenuOpen(false)}> + setIsMenuOpen(false)} + > {link.label} ))}
- {currentUser ? ( - setIsMenuOpen(false)}>Dashboard - ) : ( - <> - setIsMenuOpen(false)}>Login - setIsMenuOpen(false)}>Join Now - - )} + setIsMenuOpen(false)} + > + Log In + + setIsMenuOpen(false)} + > + Join Now +
)} -
+ -
+ {/* Main Content */} +
{children}
-