251205:0000 Just start debug backend/frontend
This commit is contained in:
95
frontend/components/search/filters.tsx
Normal file
95
frontend/components/search/filters.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
"use client";
|
||||
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { SearchFilters as FilterType } from "@/types/search";
|
||||
import { useState } from "react";
|
||||
|
||||
interface SearchFiltersProps {
|
||||
onFilterChange: (filters: FilterType) => void;
|
||||
}
|
||||
|
||||
export function SearchFilters({ onFilterChange }: SearchFiltersProps) {
|
||||
const [filters, setFilters] = useState<FilterType>({
|
||||
types: [],
|
||||
statuses: [],
|
||||
});
|
||||
|
||||
const handleTypeChange = (type: string, checked: boolean) => {
|
||||
const currentTypes = filters.types || [];
|
||||
const newTypes = checked
|
||||
? [...currentTypes, type]
|
||||
: currentTypes.filter((t) => t !== type);
|
||||
|
||||
const newFilters = { ...filters, types: newTypes };
|
||||
setFilters(newFilters);
|
||||
onFilterChange(newFilters);
|
||||
};
|
||||
|
||||
const handleStatusChange = (status: string, checked: boolean) => {
|
||||
const currentStatuses = filters.statuses || [];
|
||||
const newStatuses = checked
|
||||
? [...currentStatuses, status]
|
||||
: currentStatuses.filter((s) => s !== status);
|
||||
|
||||
const newFilters = { ...filters, statuses: newStatuses };
|
||||
setFilters(newFilters);
|
||||
onFilterChange(newFilters);
|
||||
};
|
||||
|
||||
const clearFilters = () => {
|
||||
const newFilters = { types: [], statuses: [] };
|
||||
setFilters(newFilters);
|
||||
onFilterChange(newFilters);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="p-4 space-y-6">
|
||||
<div>
|
||||
<h3 className="font-semibold mb-3">Document Type</h3>
|
||||
<div className="space-y-2">
|
||||
{["correspondence", "rfa", "drawing"].map((type) => (
|
||||
<div key={type} className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id={`type-${type}`}
|
||||
checked={filters.types?.includes(type)}
|
||||
onCheckedChange={(checked) => handleTypeChange(type, checked as boolean)}
|
||||
/>
|
||||
<Label htmlFor={`type-${type}`} className="text-sm capitalize">
|
||||
{type}
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="font-semibold mb-3">Status</h3>
|
||||
<div className="space-y-2">
|
||||
{["DRAFT", "PENDING", "APPROVED", "REJECTED", "IN_REVIEW"].map((status) => (
|
||||
<div key={status} className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id={`status-${status}`}
|
||||
checked={filters.statuses?.includes(status)}
|
||||
onCheckedChange={(checked) => handleStatusChange(status, checked as boolean)}
|
||||
/>
|
||||
<Label htmlFor={`status-${status}`} className="text-sm capitalize">
|
||||
{status.replace("_", " ").toLowerCase()}
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
onClick={clearFilters}
|
||||
>
|
||||
Clear Filters
|
||||
</Button>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
97
frontend/components/search/results.tsx
Normal file
97
frontend/components/search/results.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
"use client";
|
||||
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import Link from "next/link";
|
||||
import { FileText, Clipboard, Image, Loader2 } from "lucide-react";
|
||||
import { SearchResult } from "@/types/search";
|
||||
import { format } from "date-fns";
|
||||
|
||||
interface SearchResultsProps {
|
||||
results: SearchResult[];
|
||||
query: string;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
export function SearchResults({ results, query, loading }: SearchResultsProps) {
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex justify-center py-12">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (results.length === 0) {
|
||||
return (
|
||||
<Card className="p-12 text-center text-muted-foreground">
|
||||
{query ? `No results found for "${query}"` : "Enter a search term to start"}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
const getIcon = (type: string) => {
|
||||
switch (type) {
|
||||
case "correspondence":
|
||||
return FileText;
|
||||
case "rfa":
|
||||
return Clipboard;
|
||||
case "drawing":
|
||||
return Image;
|
||||
default:
|
||||
return FileText;
|
||||
}
|
||||
};
|
||||
|
||||
const getLink = (result: SearchResult) => {
|
||||
return `/${result.type}s/${result.id}`; // Assuming routes are plural (correspondences, rfas, drawings)
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{results.map((result, index) => {
|
||||
const Icon = getIcon(result.type);
|
||||
|
||||
return (
|
||||
<Card
|
||||
key={`${result.type}-${result.id}-${index}`}
|
||||
className="p-6 hover:shadow-md transition-shadow group"
|
||||
>
|
||||
<Link href={getLink(result)}>
|
||||
<div className="flex gap-4">
|
||||
<div className="flex-shrink-0 mt-1">
|
||||
<Icon className="h-6 w-6 text-muted-foreground group-hover:text-primary transition-colors" />
|
||||
</div>
|
||||
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-3 mb-2 flex-wrap">
|
||||
<h3
|
||||
className="text-lg font-semibold group-hover:text-primary transition-colors"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: result.highlight || result.title
|
||||
}}
|
||||
/>
|
||||
<Badge variant="secondary" className="capitalize">{result.type}</Badge>
|
||||
<Badge variant="outline">{result.status}</Badge>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-muted-foreground mb-2 line-clamp-2">
|
||||
{result.description}
|
||||
</p>
|
||||
|
||||
<div className="flex gap-4 text-xs text-muted-foreground">
|
||||
<span className="font-medium">{result.documentNumber}</span>
|
||||
<span>•</span>
|
||||
<span>
|
||||
{format(new Date(result.createdAt), "dd MMM yyyy")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user