seach bar addded in form

This commit is contained in:
2025-05-15 23:08:16 +05:30
parent ffe239783f
commit 7727ad862c
3 changed files with 82 additions and 64 deletions

View File

@@ -76,6 +76,7 @@
"tailwindcss": "^3.4.17", "tailwindcss": "^3.4.17",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"tw-animate-css": "^1.2.5", "tw-animate-css": "^1.2.5",
"use-debounce": "^10.0.4",
"vaul": "^1.1.2", "vaul": "^1.1.2",
"wouter": "^3.7.0", "wouter": "^3.7.0",
"ws": "^8.18.0", "ws": "^8.18.0",

View File

@@ -1,4 +1,4 @@
import { useEffect } from "react"; import { useEffect, useMemo, useRef, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { format } from "date-fns"; import { format } from "date-fns";
@@ -31,7 +31,7 @@ import { CalendarIcon, Clock } from "lucide-react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { useAuth } from "@/hooks/use-auth"; import { useAuth } from "@/hooks/use-auth";
import { useDebounce } from "use-debounce";
import { import {
AppointmentUncheckedCreateInputObjectSchema, AppointmentUncheckedCreateInputObjectSchema,
PatientUncheckedCreateInputObjectSchema, PatientUncheckedCreateInputObjectSchema,
@@ -83,6 +83,15 @@ export function AppointmentForm({
isLoading = false, isLoading = false,
}: AppointmentFormProps) { }: AppointmentFormProps) {
const { user } = useAuth(); const { user } = useAuth();
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
const timeout = setTimeout(() => {
inputRef.current?.focus();
}, 50); // small delay ensures content is mounted
return () => clearTimeout(timeout);
}, []);
const { data: staffMembersRaw = [] as Staff[], isLoading: isLoadingStaff } = const { data: staffMembersRaw = [] as Staff[], isLoading: isLoadingStaff } =
useQuery<Staff[]>({ useQuery<Staff[]>({
@@ -163,6 +172,24 @@ export function AppointmentForm({
defaultValues, defaultValues,
}); });
const [searchTerm, setSearchTerm] = useState("");
const [debouncedSearchTerm] = useDebounce(searchTerm, 200); // 1 seconds
const [filteredPatients, setFilteredPatients] = useState(patients);
useEffect(() => {
if (!debouncedSearchTerm.trim()) {
setFilteredPatients(patients);
} else {
const term = debouncedSearchTerm.toLowerCase();
setFilteredPatients(
patients.filter((p) =>
`${p.firstName} ${p.lastName} ${p.phone} ${p.dob}`.toLowerCase().includes(term)
)
);
}
}, [debouncedSearchTerm, patients]);
// Force form field values to update and clean up storage // Force form field values to update and clean up storage
useEffect(() => { useEffect(() => {
if (parsedStoredData) { if (parsedStoredData) {
@@ -277,14 +304,45 @@ export function AppointmentForm({
</SelectTrigger> </SelectTrigger>
</FormControl> </FormControl>
<SelectContent> <SelectContent>
{patients.map((patient) => ( <div className="p-2" onKeyDown={(e) => e.stopPropagation()}>
<Input
ref={inputRef}
placeholder="Search patients..."
className="w-full"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
onKeyDown={(e) => {
const navKeys = ['ArrowDown', 'ArrowUp', 'Enter'];
if (!navKeys.includes(e.key)) {
e.stopPropagation(); // Only stop keys that affect select state
}
}}
/>
</div>
<div className="max-h-60 overflow-y-auto scrollbar-thin scrollbar-thumb-muted-foreground/30">
{filteredPatients.length > 0 ? (
filteredPatients.map((patient) => (
<SelectItem <SelectItem
key={patient.id} key={patient.id}
value={patient.id.toString()} value={patient.id.toString()}
> ><div className="flex flex-col">
<span className="font-medium">
{patient.firstName} {patient.lastName} {patient.firstName} {patient.lastName}
</span>
<span className="text-xs text-muted-foreground">
DOB: {new Date(patient.dateOfBirth).toLocaleDateString()} {patient.phone}
</span>
</div>
</SelectItem> </SelectItem>
))} ))
) : (
<div className="p-2 text-muted-foreground text-sm">
No patients found
</div>
)}
</div>
</SelectContent> </SelectContent>
</Select> </Select>
<FormMessage /> <FormMessage />

67
package-lock.json generated
View File

@@ -471,12 +471,12 @@
"react-hook-form": "^7.55.0", "react-hook-form": "^7.55.0",
"react-icons": "^5.4.0", "react-icons": "^5.4.0",
"react-resizable-panels": "^2.1.7", "react-resizable-panels": "^2.1.7",
"react-router-dom": "^7.6.0",
"recharts": "^2.15.2", "recharts": "^2.15.2",
"tailwind-merge": "^3.2.0", "tailwind-merge": "^3.2.0",
"tailwindcss": "^3.4.17", "tailwindcss": "^3.4.17",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"tw-animate-css": "^1.2.5", "tw-animate-css": "^1.2.5",
"use-debounce": "^10.0.4",
"vaul": "^1.1.2", "vaul": "^1.1.2",
"wouter": "^3.7.0", "wouter": "^3.7.0",
"ws": "^8.18.0", "ws": "^8.18.0",
@@ -11572,53 +11572,6 @@
"react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
} }
}, },
"node_modules/react-router": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.0.tgz",
"integrity": "sha512-GGufuHIVCJDbnIAXP3P9Sxzq3UUsddG3rrI3ut1q6m0FI6vxVBF3JoPQ38+W/blslLH4a5Yutp8drkEpXoddGQ==",
"license": "MIT",
"dependencies": {
"cookie": "^1.0.1",
"set-cookie-parser": "^2.6.0"
},
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
}
}
},
"node_modules/react-router-dom": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.6.0.tgz",
"integrity": "sha512-DYgm6RDEuKdopSyGOWZGtDfSm7Aofb8CCzgkliTjtu/eDuB0gcsv6qdFhhi8HdtmA+KHkt5MfZ5K2PdzjugYsA==",
"license": "MIT",
"dependencies": {
"react-router": "7.6.0"
},
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
}
},
"node_modules/react-router/node_modules/cookie": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
"integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/react-smooth": { "node_modules/react-smooth": {
"version": "4.0.4", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz",
@@ -12266,12 +12219,6 @@
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/set-cookie-parser": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
"license": "MIT"
},
"node_modules/set-function-length": { "node_modules/set-function-length": {
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
@@ -13916,6 +13863,18 @@
} }
} }
}, },
"node_modules/use-debounce": {
"version": "10.0.4",
"resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-10.0.4.tgz",
"integrity": "sha512-6Cf7Yr7Wk7Kdv77nnJMf6de4HuDE4dTxKij+RqE9rufDsI6zsbjyAxcH5y2ueJCQAnfgKbzXbZHYlkFwmBlWkw==",
"license": "MIT",
"engines": {
"node": ">= 16.0.0"
},
"peerDependencies": {
"react": "*"
}
},
"node_modules/use-sidecar": { "node_modules/use-sidecar": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz",