Merge pull request #69 from rede5/codex/implement-search-functionality
fix(frontend): sync /jobs filters to URL and fix search layout
This commit is contained in:
commit
e6d22b87c3
1 changed files with 51 additions and 8 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useSearchParams } from "next/navigation"
|
import { usePathname, useRouter, useSearchParams } from "next/navigation"
|
||||||
import { useEffect, useState, useMemo, Suspense, useCallback } from "react"
|
import { useEffect, useState, useMemo, Suspense, useCallback } from "react"
|
||||||
import { Navbar } from "@/components/navbar"
|
import { Navbar } from "@/components/navbar"
|
||||||
import { Footer } from "@/components/footer"
|
import { Footer } from "@/components/footer"
|
||||||
|
|
@ -19,8 +19,13 @@ import { Search, MapPin, Briefcase, SlidersHorizontal, X, ArrowUpDown, Clock, Be
|
||||||
import { motion, AnimatePresence } from "framer-motion"
|
import { motion, AnimatePresence } from "framer-motion"
|
||||||
import type { Job } from "@/lib/types"
|
import type { Job } from "@/lib/types"
|
||||||
|
|
||||||
|
const WORK_MODE_OPTIONS = ["remote", "hybrid", "onsite"]
|
||||||
|
const TYPE_OPTIONS = ["full-time", "part-time", "contract", "dispatch"]
|
||||||
|
|
||||||
function JobsContent() {
|
function JobsContent() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const router = useRouter()
|
||||||
|
const pathname = usePathname()
|
||||||
const searchParams = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
const { searches, saveSearch, clearSearches, removeSearch } = useRecentSearches()
|
const { searches, saveSearch, clearSearches, removeSearch } = useRecentSearches()
|
||||||
|
|
||||||
|
|
@ -55,6 +60,17 @@ function JobsContent() {
|
||||||
const debouncedLocation = useDebounce(locationFilter, 500)
|
const debouncedLocation = useDebounce(locationFilter, 500)
|
||||||
|
|
||||||
const handleSearch = useCallback(() => {
|
const handleSearch = useCallback(() => {
|
||||||
|
const query = new URLSearchParams()
|
||||||
|
|
||||||
|
if (searchTerm.trim()) query.set("q", searchTerm.trim())
|
||||||
|
if (locationFilter.trim()) query.set("location", locationFilter.trim())
|
||||||
|
if (typeFilter !== "all") query.set("type", typeFilter)
|
||||||
|
if (workModeFilter !== "all") query.set("workMode", workModeFilter)
|
||||||
|
if (salaryMin.trim()) query.set("salary", salaryMin.trim())
|
||||||
|
|
||||||
|
const nextUrl = query.toString() ? `${pathname}?${query.toString()}` : pathname
|
||||||
|
router.replace(nextUrl, { scroll: false })
|
||||||
|
|
||||||
if (searchTerm || locationFilter) {
|
if (searchTerm || locationFilter) {
|
||||||
saveSearch({
|
saveSearch({
|
||||||
query: searchTerm,
|
query: searchTerm,
|
||||||
|
|
@ -66,7 +82,17 @@ function JobsContent() {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, [searchTerm, locationFilter, typeFilter, workModeFilter, datePostedFilter, saveSearch])
|
}, [
|
||||||
|
searchTerm,
|
||||||
|
locationFilter,
|
||||||
|
typeFilter,
|
||||||
|
workModeFilter,
|
||||||
|
datePostedFilter,
|
||||||
|
salaryMin,
|
||||||
|
pathname,
|
||||||
|
router,
|
||||||
|
saveSearch,
|
||||||
|
])
|
||||||
|
|
||||||
// Initial params
|
// Initial params
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -74,10 +100,17 @@ function JobsContent() {
|
||||||
const q = searchParams.get("q")
|
const q = searchParams.get("q")
|
||||||
const s = searchParams.get("s")
|
const s = searchParams.get("s")
|
||||||
const type = searchParams.get("type")
|
const type = searchParams.get("type")
|
||||||
|
const contractType = searchParams.get("contractType")
|
||||||
const location = searchParams.get("location")
|
const location = searchParams.get("location")
|
||||||
const l = searchParams.get("l")
|
const l = searchParams.get("l")
|
||||||
const mode = searchParams.get("mode")
|
const mode = searchParams.get("mode")
|
||||||
const workMode = searchParams.get("workMode")
|
const workMode = searchParams.get("workMode")
|
||||||
|
const salary = searchParams.get("salary")
|
||||||
|
const salaryMinParam = searchParams.get("salaryMin")
|
||||||
|
|
||||||
|
const rawType = (type || contractType || "").toLowerCase()
|
||||||
|
const rawWorkMode = (mode || workMode || "").toLowerCase()
|
||||||
|
const parsedSalary = (salaryMinParam || salary || "").replace(/[^\d.,]/g, "").replace(",", ".")
|
||||||
|
|
||||||
if (tech || q || s) {
|
if (tech || q || s) {
|
||||||
setSearchTerm(tech || q || s || "")
|
setSearchTerm(tech || q || s || "")
|
||||||
|
|
@ -89,11 +122,21 @@ function JobsContent() {
|
||||||
setShowFilters(true)
|
setShowFilters(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === "remote") {
|
if (rawType === "remote") {
|
||||||
setWorkModeFilter("remote")
|
setWorkModeFilter("remote")
|
||||||
setShowFilters(true)
|
setShowFilters(true)
|
||||||
} else if (mode || workMode) {
|
} else if (rawWorkMode && WORK_MODE_OPTIONS.includes(rawWorkMode)) {
|
||||||
setWorkModeFilter(mode || workMode || "all")
|
setWorkModeFilter(rawWorkMode)
|
||||||
|
setShowFilters(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rawType && TYPE_OPTIONS.includes(rawType)) {
|
||||||
|
setTypeFilter(rawType)
|
||||||
|
setShowFilters(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsedSalary) {
|
||||||
|
setSalaryMin(parsedSalary)
|
||||||
setShowFilters(true)
|
setShowFilters(true)
|
||||||
}
|
}
|
||||||
}, [searchParams])
|
}, [searchParams])
|
||||||
|
|
@ -194,8 +237,8 @@ function JobsContent() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hardcoded options since we don't have all data client-side
|
// Hardcoded options since we don't have all data client-side
|
||||||
const workModeOptions = ["remote", "hybrid", "onsite"]
|
const workModeOptions = WORK_MODE_OPTIONS
|
||||||
const typeOptions = ["full-time", "part-time", "contract", "dispatch"]
|
const typeOptions = TYPE_OPTIONS
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
@ -264,7 +307,7 @@ function JobsContent() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-2">
|
<div className="flex flex-wrap gap-2 lg:flex-nowrap">
|
||||||
<Button
|
<Button
|
||||||
onClick={handleSearch}
|
onClick={handleSearch}
|
||||||
className="h-12 gap-2 bg-[#F0932B] hover:bg-[#E8821C] text-white"
|
className="h-12 gap-2 bg-[#F0932B] hover:bg-[#E8821C] text-white"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue