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:
+35
-1
@@ -1,6 +1,40 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en" class="no-select">
|
||||||
<head>
|
<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" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import {
|
|||||||
AlertDialogTitle,
|
AlertDialogTitle,
|
||||||
} from "@/components/ui/alert-dialog"
|
} from "@/components/ui/alert-dialog"
|
||||||
import { useAdminBooks, useAdminSearchBooks, useAddBook, useUpdateBook, useUpdateStock, useDeleteBook, useAdminBorrowBook } from "./hooks"
|
import { useAdminBooks, useAdminSearchBooks, useAddBook, useUpdateBook, useUpdateStock, useDeleteBook, useAdminBorrowBook } from "./hooks"
|
||||||
import { usePlatform } from "@/hooks/usePlatform"
|
|
||||||
import { useIsMobile } from "@/hooks/useIsMobile"
|
import { useIsMobile } from "@/hooks/useIsMobile"
|
||||||
import type { Book } from "@/types/api"
|
import type { Book } from "@/types/api"
|
||||||
import { getErrorMessage } from "@/lib/errors"
|
import { getErrorMessage } from "@/lib/errors"
|
||||||
@@ -33,9 +32,8 @@ interface BorrowDialog { open: boolean; book: Book }
|
|||||||
const addInitial: AddDialog = { open: false }
|
const addInitial: AddDialog = { open: false }
|
||||||
|
|
||||||
export default function AdminBooksPage() {
|
export default function AdminBooksPage() {
|
||||||
const platform = usePlatform()
|
|
||||||
const isMobile = useIsMobile()
|
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 [searchText, setSearchText] = useState("")
|
||||||
const [submitted, setSubmitted] = useState("")
|
const [submitted, setSubmitted] = useState("")
|
||||||
|
|||||||
@@ -17,16 +17,14 @@ import {
|
|||||||
AlertDialogTitle,
|
AlertDialogTitle,
|
||||||
} from "@/components/ui/alert-dialog"
|
} from "@/components/ui/alert-dialog"
|
||||||
import { useAdminBorrows, useSearchBorrows, useAdminReturnBook } from "./hooks"
|
import { useAdminBorrows, useSearchBorrows, useAdminReturnBook } from "./hooks"
|
||||||
import { usePlatform } from "@/hooks/usePlatform"
|
|
||||||
import { useIsMobile } from "@/hooks/useIsMobile"
|
import { useIsMobile } from "@/hooks/useIsMobile"
|
||||||
import { formatDateTime } from "@/lib/formatters"
|
import { formatDateTime } from "@/lib/formatters"
|
||||||
import { getErrorMessage } from "@/lib/errors"
|
import { getErrorMessage } from "@/lib/errors"
|
||||||
import type { BorrowInfoVo } from "@/types/api"
|
import type { BorrowInfoVo } from "@/types/api"
|
||||||
|
|
||||||
export default function AdminBorrowsPage() {
|
export default function AdminBorrowsPage() {
|
||||||
const platform = usePlatform()
|
|
||||||
const isMobile = useIsMobile()
|
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 [searchText, setSearchText] = useState("")
|
||||||
const [submitted, setSubmitted] = useState("")
|
const [submitted, setSubmitted] = useState("")
|
||||||
|
|||||||
@@ -7,16 +7,14 @@ import { Input } from "@/components/ui/input"
|
|||||||
import { Skeleton } from "@/components/ui/skeleton"
|
import { Skeleton } from "@/components/ui/skeleton"
|
||||||
import { useBooks, useSearchBooks, useBorrowBook } from "./hooks"
|
import { useBooks, useSearchBooks, useBorrowBook } from "./hooks"
|
||||||
import { useAuthStore } from "@/store/authStore"
|
import { useAuthStore } from "@/store/authStore"
|
||||||
import { usePlatform } from "@/hooks/usePlatform"
|
|
||||||
import { useIsMobile } from "@/hooks/useIsMobile"
|
import { useIsMobile } from "@/hooks/useIsMobile"
|
||||||
import { getErrorMessage } from "@/lib/errors"
|
import { getErrorMessage } from "@/lib/errors"
|
||||||
|
|
||||||
export default function BooksPage() {
|
export default function BooksPage() {
|
||||||
const borrow = useBorrowBook()
|
const borrow = useBorrowBook()
|
||||||
const token = useAuthStore((s) => s.token)
|
const token = useAuthStore((s) => s.token)
|
||||||
const platform = usePlatform()
|
|
||||||
const isMobile = useIsMobile()
|
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 [searchText, setSearchText] = useState("")
|
||||||
const [submitted, setSubmitted] = useState("")
|
const [submitted, setSubmitted] = useState("")
|
||||||
|
|||||||
@@ -8,15 +8,13 @@ import { Input } from "@/components/ui/input"
|
|||||||
import { Skeleton } from "@/components/ui/skeleton"
|
import { Skeleton } from "@/components/ui/skeleton"
|
||||||
import { useMyBorrows, useSearchMyBorrows, useReturnBook } from "./hooks"
|
import { useMyBorrows, useSearchMyBorrows, useReturnBook } from "./hooks"
|
||||||
import { formatDateTime } from "@/lib/formatters"
|
import { formatDateTime } from "@/lib/formatters"
|
||||||
import { usePlatform } from "@/hooks/usePlatform"
|
|
||||||
import { useIsMobile } from "@/hooks/useIsMobile"
|
import { useIsMobile } from "@/hooks/useIsMobile"
|
||||||
import { getErrorMessage } from "@/lib/errors"
|
import { getErrorMessage } from "@/lib/errors"
|
||||||
|
|
||||||
export default function MyBorrowsPage() {
|
export default function MyBorrowsPage() {
|
||||||
const returnBook = useReturnBook()
|
const returnBook = useReturnBook()
|
||||||
const platform = usePlatform()
|
|
||||||
const isMobile = useIsMobile()
|
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 [searchText, setSearchText] = useState("")
|
||||||
const [submitted, setSubmitted] = useState("")
|
const [submitted, setSubmitted] = useState("")
|
||||||
|
|||||||
@@ -8,12 +8,6 @@ function isTauri(): boolean {
|
|||||||
return !!(window as unknown as Record<string, unknown>).__TAURI_INTERNALS__
|
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 }) {
|
export function PlatformProvider({ children }: { children: ReactNode }) {
|
||||||
const [platform, setPlatform] = useState<Platform>(isTauri() ? "desktop" : "web")
|
const [platform, setPlatform] = useState<Platform>(isTauri() ? "desktop" : "web")
|
||||||
|
|
||||||
|
|||||||
+4
-1
@@ -140,11 +140,14 @@ html.mobile body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
html.no-select * {
|
html.no-select * {
|
||||||
|
-webkit-user-select: none !important;
|
||||||
user-select: none !important;
|
user-select: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.no-select input,
|
html.no-select input,
|
||||||
html.no-select textarea,
|
html.no-select textarea,
|
||||||
html.no-select [data-selectable] {
|
html.no-select [data-selectable] {
|
||||||
|
-webkit-user-select: text !important;
|
||||||
user-select: text !important;
|
user-select: text !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user