seach bar addded in form
This commit is contained in:
@@ -76,6 +76,7 @@
|
||||
"tailwindcss": "^3.4.17",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"tw-animate-css": "^1.2.5",
|
||||
"use-debounce": "^10.0.4",
|
||||
"vaul": "^1.1.2",
|
||||
"wouter": "^3.7.0",
|
||||
"ws": "^8.18.0",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { format } from "date-fns";
|
||||
@@ -31,7 +31,7 @@ import { CalendarIcon, Clock } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
|
||||
import { useDebounce } from "use-debounce";
|
||||
import {
|
||||
AppointmentUncheckedCreateInputObjectSchema,
|
||||
PatientUncheckedCreateInputObjectSchema,
|
||||
@@ -83,6 +83,15 @@ export function AppointmentForm({
|
||||
isLoading = false,
|
||||
}: AppointmentFormProps) {
|
||||
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 } =
|
||||
useQuery<Staff[]>({
|
||||
@@ -163,6 +172,24 @@ export function AppointmentForm({
|
||||
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
|
||||
useEffect(() => {
|
||||
if (parsedStoredData) {
|
||||
@@ -277,14 +304,45 @@ export function AppointmentForm({
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{patients.map((patient) => (
|
||||
<SelectItem
|
||||
key={patient.id}
|
||||
value={patient.id.toString()}
|
||||
>
|
||||
{patient.firstName} {patient.lastName}
|
||||
</SelectItem>
|
||||
))}
|
||||
<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
|
||||
key={patient.id}
|
||||
value={patient.id.toString()}
|
||||
><div className="flex flex-col">
|
||||
<span className="font-medium">
|
||||
{patient.firstName} {patient.lastName}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
DOB: {new Date(patient.dateOfBirth).toLocaleDateString()} • {patient.phone}
|
||||
</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))
|
||||
) : (
|
||||
<div className="p-2 text-muted-foreground text-sm">
|
||||
No patients found
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
|
||||
67
package-lock.json
generated
67
package-lock.json
generated
@@ -471,12 +471,12 @@
|
||||
"react-hook-form": "^7.55.0",
|
||||
"react-icons": "^5.4.0",
|
||||
"react-resizable-panels": "^2.1.7",
|
||||
"react-router-dom": "^7.6.0",
|
||||
"recharts": "^2.15.2",
|
||||
"tailwind-merge": "^3.2.0",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"tw-animate-css": "^1.2.5",
|
||||
"use-debounce": "^10.0.4",
|
||||
"vaul": "^1.1.2",
|
||||
"wouter": "^3.7.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"
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz",
|
||||
@@ -12266,12 +12219,6 @@
|
||||
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
|
||||
"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": {
|
||||
"version": "1.2.2",
|
||||
"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": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz",
|
||||
|
||||
Reference in New Issue
Block a user