mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
change design and queries to api
This commit is contained in:
@@ -20,7 +20,9 @@ import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Check, ChevronsUpDown, X, Users } from 'lucide-react';
|
||||
import { Check, ChevronsUpDown, X, Users, ChevronLeft, ChevronRight } from 'lucide-react';
|
||||
|
||||
const PAGE_SIZE = 5;
|
||||
|
||||
export default function GroupMultiSelect({
|
||||
value = [],
|
||||
@@ -31,6 +33,7 @@ export default function GroupMultiSelect({
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [search, setSearch] = useState('');
|
||||
const [page, setPage] = useState(1);
|
||||
const [allGroups, setAllGroups] = useState(groupsProp ?? []);
|
||||
const [loadingGroups, setLoadingGroups] = useState(!groupsProp);
|
||||
const [dropdownStyle, setDropdownStyle] = useState({});
|
||||
@@ -109,6 +112,17 @@ export default function GroupMultiSelect({
|
||||
const filteredIds = filtered.map((g) => g.group_id);
|
||||
const allFilteredSelected = filteredIds.length > 0 && filteredIds.every((id) => value.includes(id));
|
||||
|
||||
// ── Pagination ────────────────────────────────────────────────────────────
|
||||
const totalPages = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE));
|
||||
const safePage = Math.min(page, totalPages);
|
||||
const paginated = filtered.slice((safePage - 1) * PAGE_SIZE, safePage * PAGE_SIZE);
|
||||
|
||||
// Reset to page 1 whenever the search term changes
|
||||
const handleSearchChange = (e) => {
|
||||
setSearch(e.target.value);
|
||||
setPage(1);
|
||||
};
|
||||
|
||||
const toggle = (groupId) =>
|
||||
onChange(value.includes(groupId)
|
||||
? value.filter((id) => id !== groupId)
|
||||
@@ -141,7 +155,7 @@ export default function GroupMultiSelect({
|
||||
<Input
|
||||
autoFocus
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
onChange={handleSearchChange}
|
||||
placeholder="Search groups…"
|
||||
className="h-8 text-sm"
|
||||
/>
|
||||
@@ -173,7 +187,7 @@ export default function GroupMultiSelect({
|
||||
)}
|
||||
|
||||
{/* Options */}
|
||||
<ul className="max-h-52 overflow-y-auto py-1">
|
||||
<ul className="py-1">
|
||||
{loadingGroups ? (
|
||||
<li className="px-3 py-6 text-center text-xs text-muted-foreground">
|
||||
Loading groups…
|
||||
@@ -183,7 +197,7 @@ export default function GroupMultiSelect({
|
||||
No groups found.
|
||||
</li>
|
||||
) : (
|
||||
filtered.map((g) => {
|
||||
paginated.map((g) => {
|
||||
const selected = value.includes(g.group_id);
|
||||
return (
|
||||
<li
|
||||
@@ -211,6 +225,35 @@ export default function GroupMultiSelect({
|
||||
})
|
||||
)}
|
||||
</ul>
|
||||
|
||||
{/* Pagination */}
|
||||
{!loadingGroups && filtered.length > PAGE_SIZE && (
|
||||
<div className="flex items-center justify-between px-3 py-1.5 border-t border-border bg-muted/40">
|
||||
<button
|
||||
type="button"
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
onClick={() => setPage((p) => Math.max(1, p - 1))}
|
||||
disabled={safePage <= 1}
|
||||
className="flex items-center gap-0.5 text-xs text-muted-foreground hover:text-foreground disabled:opacity-40 disabled:pointer-events-none"
|
||||
>
|
||||
<ChevronLeft className="h-3.5 w-3.5" />
|
||||
Prev
|
||||
</button>
|
||||
<span className="text-xs text-muted-foreground tabular-nums">
|
||||
{safePage} / {totalPages}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
onClick={() => setPage((p) => Math.min(totalPages, p + 1))}
|
||||
disabled={safePage >= totalPages}
|
||||
className="flex items-center gap-0.5 text-xs text-muted-foreground hover:text-foreground disabled:opacity-40 disabled:pointer-events-none"
|
||||
>
|
||||
Next
|
||||
<ChevronRight className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>,
|
||||
document.body
|
||||
);
|
||||
@@ -222,7 +265,7 @@ export default function GroupMultiSelect({
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
onClick={() => { setOpen((o) => !o); setSearch(''); }}
|
||||
onClick={() => { setOpen((o) => !o); setSearch(''); setPage(1); }}
|
||||
className={cn(
|
||||
'w-full min-h-9 px-3 py-1.5 rounded-md border border-input bg-background text-sm',
|
||||
'flex items-center gap-1.5 text-left',
|
||||
|
||||
@@ -20,7 +20,9 @@ import { createPortal } from 'react-dom';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Check, ChevronsUpDown, X, ListChecks } from 'lucide-react';
|
||||
import { Check, ChevronsUpDown, X, ListChecks, ChevronLeft, ChevronRight } from 'lucide-react';
|
||||
|
||||
const PAGE_SIZE = 5;
|
||||
|
||||
export default function TaskMultiSelect({
|
||||
value = [],
|
||||
@@ -31,6 +33,7 @@ export default function TaskMultiSelect({
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [search, setSearch] = useState('');
|
||||
const [page, setPage] = useState(1);
|
||||
const [dropdownStyle, setDropdownStyle] = useState({});
|
||||
|
||||
const triggerRef = useRef(null);
|
||||
@@ -86,6 +89,17 @@ export default function TaskMultiSelect({
|
||||
const filteredIds = filtered.map((t) => t.task_id);
|
||||
const allFilteredSelected = filteredIds.length > 0 && filteredIds.every((id) => value.includes(id));
|
||||
|
||||
// ── Pagination ────────────────────────────────────────────────────────────
|
||||
const totalPages = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE));
|
||||
const safePage = Math.min(page, totalPages);
|
||||
const paginated = filtered.slice((safePage - 1) * PAGE_SIZE, safePage * PAGE_SIZE);
|
||||
|
||||
// Reset to page 1 whenever the search term changes
|
||||
const handleSearchChange = (e) => {
|
||||
setSearch(e.target.value);
|
||||
setPage(1);
|
||||
};
|
||||
|
||||
const toggle = (taskId) =>
|
||||
onChange(value.includes(taskId)
|
||||
? value.filter((id) => id !== taskId)
|
||||
@@ -118,7 +132,7 @@ export default function TaskMultiSelect({
|
||||
<Input
|
||||
autoFocus
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
onChange={handleSearchChange}
|
||||
placeholder="Search tasks…"
|
||||
className="h-8 text-sm"
|
||||
/>
|
||||
@@ -150,13 +164,13 @@ export default function TaskMultiSelect({
|
||||
)}
|
||||
|
||||
{/* Options */}
|
||||
<ul className="max-h-52 overflow-y-auto py-1">
|
||||
<ul className="py-1">
|
||||
{filtered.length === 0 ? (
|
||||
<li className="px-3 py-6 text-center text-xs text-muted-foreground">
|
||||
No tasks found.
|
||||
</li>
|
||||
) : (
|
||||
filtered.map((t) => {
|
||||
paginated.map((t) => {
|
||||
const selected = value.includes(t.task_id);
|
||||
return (
|
||||
<li
|
||||
@@ -184,6 +198,35 @@ export default function TaskMultiSelect({
|
||||
})
|
||||
)}
|
||||
</ul>
|
||||
|
||||
{/* Pagination */}
|
||||
{filtered.length > PAGE_SIZE && (
|
||||
<div className="flex items-center justify-between px-3 py-1.5 border-t border-border bg-muted/40">
|
||||
<button
|
||||
type="button"
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
onClick={() => setPage((p) => Math.max(1, p - 1))}
|
||||
disabled={safePage <= 1}
|
||||
className="flex items-center gap-0.5 text-xs text-muted-foreground hover:text-foreground disabled:opacity-40 disabled:pointer-events-none"
|
||||
>
|
||||
<ChevronLeft className="h-3.5 w-3.5" />
|
||||
Prev
|
||||
</button>
|
||||
<span className="text-xs text-muted-foreground tabular-nums">
|
||||
{safePage} / {totalPages}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
onClick={() => setPage((p) => Math.min(totalPages, p + 1))}
|
||||
disabled={safePage >= totalPages}
|
||||
className="flex items-center gap-0.5 text-xs text-muted-foreground hover:text-foreground disabled:opacity-40 disabled:pointer-events-none"
|
||||
>
|
||||
Next
|
||||
<ChevronRight className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>,
|
||||
document.body
|
||||
);
|
||||
@@ -195,7 +238,7 @@ export default function TaskMultiSelect({
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
onClick={() => { setOpen((o) => !o); setSearch(''); }}
|
||||
onClick={() => { setOpen((o) => !o); setSearch(''); setPage(1); }}
|
||||
className={cn(
|
||||
'w-full min-h-9 px-3 py-1.5 rounded-md border border-input bg-background text-sm',
|
||||
'flex items-center gap-1.5 text-left',
|
||||
|
||||
Reference in New Issue
Block a user