feat(admin): add search with submit and safe-area layout

Add submit-based search to admin books page and switch borrows
search from debounced to submit-based for consistency. Update
layout headers and nav bars to respect mobile safe-area insets
via CSS custom properties.
This commit is contained in:
2026-05-24 22:33:59 +08:00
parent c474d2df2f
commit 86cbd56a99
7 changed files with 43 additions and 20 deletions
+35 -1
View File
@@ -1,6 +1,40 @@
<!doctype html>
<html lang="en">
<html lang="en" class="no-select">
<head>
<script>
if (window.__TAURI_INTERNALS__) {
function allowsSelection(el) {
let cur = el
while (cur) {
if (
cur.tagName === "INPUT" ||
cur.tagName === "TEXTAREA" ||
cur.hasAttribute("data-selectable")
)
return true
cur = cur.parentElement
}
return false
}
document.addEventListener("selectstart", (e) => {
if (!allowsSelection(e.target)) e.preventDefault()
})
document.addEventListener("selectionchange", () => {
const sel = window.getSelection()
if (!sel || sel.isCollapsed) return
const node = sel.anchorNode
if (!node) return
const el = node.nodeType === 3 ? node.parentElement : node
if (el && !allowsSelection(el)) {
sel.removeAllRanges()
}
})
} else {
document.documentElement.classList.remove("no-select")
}
</script>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
+1 -3
View File
@@ -18,7 +18,6 @@ import {
AlertDialogTitle,
} from "@/components/ui/alert-dialog"
import { useAdminBooks, useAdminSearchBooks, useAddBook, useUpdateBook, useUpdateStock, useDeleteBook, useAdminBorrowBook } from "./hooks"
import { usePlatform } from "@/hooks/usePlatform"
import { useIsMobile } from "@/hooks/useIsMobile"
import type { Book } from "@/types/api"
import { getErrorMessage } from "@/lib/errors"
@@ -33,9 +32,8 @@ interface BorrowDialog { open: boolean; book: Book }
const addInitial: AddDialog = { open: false }
export default function AdminBooksPage() {
const platform = usePlatform()
const isMobile = useIsMobile()
const sel = platform === "web" ? ({ "data-selectable": true } as const) : ({} as const)
const sel = { "data-selectable": true } as const
const [searchText, setSearchText] = useState("")
const [submitted, setSubmitted] = useState("")
+1 -3
View File
@@ -17,16 +17,14 @@ import {
AlertDialogTitle,
} from "@/components/ui/alert-dialog"
import { useAdminBorrows, useSearchBorrows, useAdminReturnBook } from "./hooks"
import { usePlatform } from "@/hooks/usePlatform"
import { useIsMobile } from "@/hooks/useIsMobile"
import { formatDateTime } from "@/lib/formatters"
import { getErrorMessage } from "@/lib/errors"
import type { BorrowInfoVo } from "@/types/api"
export default function AdminBorrowsPage() {
const platform = usePlatform()
const isMobile = useIsMobile()
const sel = platform === "web" ? ({ "data-selectable": true } as const) : ({} as const)
const sel = { "data-selectable": true } as const
const [searchText, setSearchText] = useState("")
const [submitted, setSubmitted] = useState("")
+1 -3
View File
@@ -7,16 +7,14 @@ import { Input } from "@/components/ui/input"
import { Skeleton } from "@/components/ui/skeleton"
import { useBooks, useSearchBooks, useBorrowBook } from "./hooks"
import { useAuthStore } from "@/store/authStore"
import { usePlatform } from "@/hooks/usePlatform"
import { useIsMobile } from "@/hooks/useIsMobile"
import { getErrorMessage } from "@/lib/errors"
export default function BooksPage() {
const borrow = useBorrowBook()
const token = useAuthStore((s) => s.token)
const platform = usePlatform()
const isMobile = useIsMobile()
const sel = platform === "web" ? ({ "data-selectable": true } as const) : ({} as const)
const sel = { "data-selectable": true } as const
const [searchText, setSearchText] = useState("")
const [submitted, setSubmitted] = useState("")
+1 -3
View File
@@ -8,15 +8,13 @@ import { Input } from "@/components/ui/input"
import { Skeleton } from "@/components/ui/skeleton"
import { useMyBorrows, useSearchMyBorrows, useReturnBook } from "./hooks"
import { formatDateTime } from "@/lib/formatters"
import { usePlatform } from "@/hooks/usePlatform"
import { useIsMobile } from "@/hooks/useIsMobile"
import { getErrorMessage } from "@/lib/errors"
export default function MyBorrowsPage() {
const returnBook = useReturnBook()
const platform = usePlatform()
const isMobile = useIsMobile()
const sel = platform === "web" ? ({ "data-selectable": true } as const) : ({} as const)
const sel = { "data-selectable": true } as const
const [searchText, setSearchText] = useState("")
const [submitted, setSubmitted] = useState("")
-6
View File
@@ -8,12 +8,6 @@ function isTauri(): boolean {
return !!(window as unknown as Record<string, unknown>).__TAURI_INTERNALS__
}
// Default to safe: add no-select immediately, remove only if definitely web
document.documentElement.classList.add("no-select")
if (!isTauri()) {
document.documentElement.classList.remove("no-select")
}
export function PlatformProvider({ children }: { children: ReactNode }) {
const [platform, setPlatform] = useState<Platform>(isTauri() ? "desktop" : "web")
+3
View File
@@ -140,11 +140,14 @@ html.mobile body {
}
html.no-select * {
-webkit-user-select: none !important;
user-select: none !important;
}
html.no-select input,
html.no-select textarea,
html.no-select [data-selectable] {
-webkit-user-select: text !important;
user-select: text !important;
}