Quick Search working again
This commit is contained in:
parent
e15373c169
commit
c1a9bb6465
5 changed files with 6736 additions and 77 deletions
File diff suppressed because it is too large
Load diff
Binary file not shown.
|
|
@ -25,6 +25,17 @@
|
|||
- [7.1. Pro-Tip: Update your package.json](#71-pro-tip-update-your-packagejson)
|
||||
- [8. Where we are up to 8/1/2026](#8-where-we-are-up-to-812026)
|
||||
- [9. Testing Creating And Uploading Folders and Files](#9-testing-creating-and-uploading-folders-and-files)
|
||||
- [10. 📚 WebCalibre Development Notes 17/1/2026](#10--webcalibre-development-notes-1712026)
|
||||
- [10.1. 🚀 Project Overview](#101--project-overview)
|
||||
- [10.2. 🛠 Critical Technical Learnings](#102--critical-technical-learnings)
|
||||
- [10.2.1. MUI X v8 DataGrid Toolbar Fix](#1021-mui-x-v8-datagrid-toolbar-fix)
|
||||
- [10.2.2. Global "Hidden" Search](#1022-global-hidden-search)
|
||||
- [10.2.3. Server-Side Extraction](#1023-server-side-extraction)
|
||||
- [10.3. 📑 Essential Commands](#103--essential-commands)
|
||||
- [10.4. 📅 Future Roadmap](#104--future-roadmap)
|
||||
- [10.4.1. Phase 1: Metadata Utility (In Progress)](#1041-phase-1-metadata-utility-in-progress)
|
||||
- [10.4.2. Phase 2: UI Enhancements](#1042-phase-2-ui-enhancements)
|
||||
- [10.5. 💡 Reminders](#105--reminders)
|
||||
|
||||
# 1. Reference
|
||||
|
||||
|
|
@ -370,3 +381,61 @@ For download there 2 function we need to implement
|
|||
2) if the file is a pdf open the file in another tab in the browser as most browser support reading a pdf
|
||||
|
||||
So we need to implement 2 new actions, downLoad file and open pdf to read. Does this make sense ?
|
||||
|
||||
# 10. 📚 WebCalibre Development Notes 17/1/2026
|
||||
|
||||
## 10.1. 🚀 Project Overview
|
||||
A Next.js (App Router) dashboard for managing a OneDrive-synced file library.
|
||||
Uses **Prisma + PostgreSQL** for metadata storage and **MUI X v8 (DataGrid)** for the frontend.
|
||||
|
||||
---
|
||||
|
||||
## 10.2. 🛠 Critical Technical Learnings
|
||||
|
||||
### 10.2.1. MUI X v8 DataGrid Toolbar Fix
|
||||
* **Problem:** The Search Bar and "Library" title often disappear even when defined in `slots`.
|
||||
* **Solution:** - You MUST include the `showToolbar` prop on the `<DataGrid />`.
|
||||
- Use the **Persistent Quick Filter** model: set the `expanded` prop on the `<QuickFilter />` component within the custom toolbar to ensure it doesn't collapse.
|
||||
- Use `slotProps.textField` (not `input`) to customize the search input in MUI v7/v8.
|
||||
|
||||
### 10.2.2. Global "Hidden" Search
|
||||
* To allow the search bar to find "Author", "ISBN", or "Dimensions" without cluttering the UI:
|
||||
- Create a column (e.g., `metadata_search`) with `width: 0`.
|
||||
- Use a `valueGetter` to return `JSON.stringify(row.metadata)`.
|
||||
- Hide the column in `initialState.columns.columnVisibilityModel`.
|
||||
- The DataGrid filtering engine will now index this hidden string.
|
||||
|
||||
### 10.2.3. Server-Side Extraction
|
||||
* Metadata extraction from file binaries (PDF, EPUB, Images) **must** occur on the server.
|
||||
* Libraries like `sharp` and `pdf-parse` are Node.js-based and will crash if imported into client components.
|
||||
|
||||
---
|
||||
|
||||
## 10.3. 📑 Essential Commands
|
||||
|
||||
| Command | Purpose |
|
||||
| :--- | :--- |
|
||||
| `npx tsc --noEmit` | **Run this frequently.** Validates types across the entire project. Finds errors your IDE might miss. |
|
||||
| `npx prisma generate` | Updates the Prisma Client types after schema changes. |
|
||||
| `npx prisma db push` | Pushes schema changes to the PostgreSQL database. |
|
||||
| `npm run dev` | Starts the development server. |
|
||||
|
||||
---
|
||||
|
||||
## 10.4. 📅 Future Roadmap
|
||||
|
||||
### 10.4.1. Phase 1: Metadata Utility (In Progress)
|
||||
- [ ] Implement `src/lib/metadata-extractor.ts`.
|
||||
- [ ] Integrate extraction into the `syncOneDrive` Server Action.
|
||||
- [ ] Supported formats: PDF (`pdf-parse`), EPUB (`node-epub-utils`), Images (`sharp`).
|
||||
|
||||
### 10.4.2. Phase 2: UI Enhancements
|
||||
- [ ] Add "Last Synced" timestamp state to the dashboard header.
|
||||
- [ ] Add dedicated sortable columns for "Author" and "File Type".
|
||||
- [ ] Implement folder-specific row styling.
|
||||
|
||||
---
|
||||
|
||||
## 10.5. 💡 Reminders
|
||||
* **Hard Refresh:** If the DataGrid UI behaves weirdly after code changes, use `Cmd + Shift + R` or `Ctrl + F5`.
|
||||
* **Z-Index:** The search bar animation uses `gridArea: '1 / 1'`. If icons overlap, check the styled-components logic in `dashboard-view.tsx`.
|
||||
BIN
docs/notes.pdf
BIN
docs/notes.pdf
Binary file not shown.
|
|
@ -1,6 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import { useState } from "react";
|
||||
import { styled } from '@mui/material/styles';
|
||||
import {
|
||||
Button,
|
||||
CircularProgress,
|
||||
|
|
@ -16,10 +17,10 @@ import {
|
|||
import {
|
||||
DataGrid,
|
||||
GridColDef,
|
||||
GridToolbarContainer,
|
||||
// Using the new non-deprecated QuickFilter components
|
||||
Toolbar,
|
||||
QuickFilter,
|
||||
QuickFilterControl,
|
||||
QuickFilterClear,
|
||||
} from "@mui/x-data-grid";
|
||||
import SyncIcon from "@mui/icons-material/Sync";
|
||||
import RefreshIcon from "@mui/icons-material/Refresh";
|
||||
|
|
@ -28,6 +29,7 @@ import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile";
|
|||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import EditIcon from "@mui/icons-material/Edit";
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import DownloadIcon from '@mui/icons-material/Download';
|
||||
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
|
||||
|
||||
|
|
@ -35,25 +37,31 @@ import { syncOneDrive } from "./sync-actions";
|
|||
import { deleteFileNodeAction } from "./actions";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
/**
|
||||
* UPDATED TOOLBAR: Uses the new QuickFilter structure to avoid deprecation
|
||||
*/
|
||||
// --- 1. Styled Component for Search Placement ---
|
||||
|
||||
const StyledQuickFilter = styled(QuickFilter)({
|
||||
marginLeft: 'auto', // Pushes the search box to the right side of the toolbar
|
||||
});
|
||||
|
||||
// --- 2. Custom Toolbar Component ---
|
||||
|
||||
function CustomToolbar() {
|
||||
return (
|
||||
<GridToolbarContainer sx={{ p: 2, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Toolbar sx={{ p: 2, borderBottom: '1px solid', borderColor: 'divider' }}>
|
||||
<Typography variant="h6" fontWeight="bold" color="primary">
|
||||
Library
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<QuickFilter>
|
||||
|
||||
{/* The 'expanded' prop ensures the search input is always visible by default */}
|
||||
<StyledQuickFilter expanded>
|
||||
<QuickFilterControl
|
||||
render={(props) => (
|
||||
render={({ ref, ...other }) => (
|
||||
<TextField
|
||||
{...props}
|
||||
variant="outlined"
|
||||
{...other}
|
||||
sx={{ width: 300 }}
|
||||
inputRef={ref}
|
||||
placeholder="Search library..."
|
||||
size="small"
|
||||
placeholder="Search files..."
|
||||
sx={{ width: 350 }}
|
||||
slotProps={{
|
||||
input: {
|
||||
startAdornment: (
|
||||
|
|
@ -61,17 +69,32 @@ function CustomToolbar() {
|
|||
<SearchIcon fontSize="small" />
|
||||
</InputAdornment>
|
||||
),
|
||||
endAdornment: other.value ? (
|
||||
<InputAdornment position="end">
|
||||
<QuickFilterClear
|
||||
edge="end"
|
||||
size="small"
|
||||
material={{ sx: { marginRight: -0.75 } }}
|
||||
>
|
||||
<CancelIcon fontSize="small" />
|
||||
</QuickFilterClear>
|
||||
</InputAdornment>
|
||||
) : null,
|
||||
// Ensure other props are spread correctly
|
||||
...other.slotProps?.input,
|
||||
},
|
||||
...other.slotProps,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</QuickFilter>
|
||||
</Box>
|
||||
</GridToolbarContainer>
|
||||
</StyledQuickFilter>
|
||||
</Toolbar>
|
||||
);
|
||||
}
|
||||
|
||||
// --- 3. Main Dashboard View ---
|
||||
|
||||
interface DashboardViewProps {
|
||||
initialFiles: any[];
|
||||
user?: {
|
||||
|
|
@ -122,14 +145,6 @@ export default function DashboardView({ initialFiles, user }: DashboardViewProps
|
|||
}
|
||||
};
|
||||
|
||||
const handleDownload = (id: string) => {
|
||||
window.location.href = `/api/download?id=${id}&mode=attachment`;
|
||||
};
|
||||
|
||||
const handleViewInTab = (id: string) => {
|
||||
window.open(`/api/download?id=${id}&mode=inline`, '_blank');
|
||||
};
|
||||
|
||||
const columns: GridColDef[] = [
|
||||
{
|
||||
field: "name",
|
||||
|
|
@ -156,7 +171,7 @@ export default function DashboardView({ initialFiles, user }: DashboardViewProps
|
|||
width: 120,
|
||||
valueGetter: (value, row) => row.metadata?.type || (row.isFolder ? "Folder" : "File"),
|
||||
renderCell: (params) => (
|
||||
<Typography variant="caption" sx={{ textTransform: 'uppercase', fontWeight: 'bold', color: 'text.secondary' }}>
|
||||
<Typography variant="caption" sx={{ textTransform: 'uppercase', fontWeight: 'bold' }}>
|
||||
{params.value}
|
||||
</Typography>
|
||||
)
|
||||
|
|
@ -167,6 +182,12 @@ export default function DashboardView({ initialFiles, user }: DashboardViewProps
|
|||
width: 100,
|
||||
renderCell: (params) => params.row.isFolder ? "--" : `${(Number(params.value) / 1024 / 1024).toFixed(2)} MB`
|
||||
},
|
||||
{
|
||||
field: "metadata_search",
|
||||
headerName: "Search Metadata",
|
||||
width: 0,
|
||||
valueGetter: (value, row) => row.metadata ? JSON.stringify(row.metadata) : ""
|
||||
},
|
||||
{
|
||||
field: "actions",
|
||||
headerName: "Actions",
|
||||
|
|
@ -175,44 +196,26 @@ export default function DashboardView({ initialFiles, user }: DashboardViewProps
|
|||
renderCell: (params) => {
|
||||
const isOwner = params.row.ownerId === user?.id;
|
||||
const isFolder = params.row.isFolder;
|
||||
|
||||
return (
|
||||
<Stack direction="row" spacing={0.5} justifyContent="flex-end" alignItems="center" sx={{ height: '100%' }}>
|
||||
{!isFolder && (
|
||||
<>
|
||||
<Tooltip title="View in Tab">
|
||||
<IconButton size="small" color="info" onClick={() => handleViewInTab(params.row.id)}>
|
||||
<IconButton size="small" color="info" onClick={() => window.open(`/api/download?id=${params.row.id}&mode=inline`, '_blank')}>
|
||||
<OpenInNewIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="Download to Folder">
|
||||
<IconButton size="small" color="success" onClick={() => handleDownload(params.row.id)}>
|
||||
<IconButton size="small" color="success" onClick={() => window.location.href = `/api/download?id=${params.row.id}&mode=attachment`}>
|
||||
<DownloadIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
|
||||
{(isAdmin || isOwner) && (
|
||||
<>
|
||||
<Tooltip title="Edit Details">
|
||||
<IconButton
|
||||
size="small"
|
||||
color="primary"
|
||||
onClick={() => router.push(`/update/${params.row.id}`)}
|
||||
>
|
||||
<IconButton size="small" color="primary" onClick={() => router.push(`/update/${params.row.id}`)}>
|
||||
<EditIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="Delete">
|
||||
<IconButton
|
||||
size="small"
|
||||
color="error"
|
||||
onClick={() => handleDelete(params.row.id, params.row.name)}
|
||||
>
|
||||
<IconButton size="small" color="error" onClick={() => handleDelete(params.row.id, params.row.name)}>
|
||||
<DeleteIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
|
|
@ -229,7 +232,7 @@ export default function DashboardView({ initialFiles, user }: DashboardViewProps
|
|||
startIcon={isRefreshing ? <CircularProgress size={16} /> : <RefreshIcon />}
|
||||
onClick={handleRefresh}
|
||||
>
|
||||
Refresh List
|
||||
Refresh
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
|
|
@ -241,17 +244,22 @@ export default function DashboardView({ initialFiles, user }: DashboardViewProps
|
|||
</Button>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ height: 700, width: "100%", bgcolor: 'background.paper', borderRadius: 3, boxShadow: 1, overflow: 'hidden' }}>
|
||||
<Box sx={{ height: 750, width: "100%", bgcolor: 'background.paper', borderRadius: 3, boxShadow: 1, overflow: 'hidden' }}>
|
||||
<DataGrid
|
||||
rows={initialFiles}
|
||||
columns={columns}
|
||||
// --- THE FIX: showToolbar must be true, and toolbar slot must be assigned ---
|
||||
showToolbar
|
||||
slots={{ toolbar: CustomToolbar }}
|
||||
disableRowSelectionOnClick
|
||||
sx={{
|
||||
border: 'none',
|
||||
'& .MuiDataGrid-columnHeader': { bgcolor: '#f8f9fa' },
|
||||
'& .MuiDataGrid-footerContainer': { borderTop: '1px solid #eee' }
|
||||
initialState={{
|
||||
columns: {
|
||||
columnVisibilityModel: {
|
||||
metadata_search: false,
|
||||
},
|
||||
},
|
||||
}}
|
||||
sx={{ border: 'none' }}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
|
|
|||
Loading…
Reference in a new issue