diff --git a/src/app/dashboard/[entity]/page.tsx b/src/app/dashboard/[entity]/page.tsx index 0f224bb..0ca24a6 100644 --- a/src/app/dashboard/[entity]/page.tsx +++ b/src/app/dashboard/[entity]/page.tsx @@ -1,14 +1,25 @@ import { APPWRITE_ALMEIRA_CONFIG } from '@/lib/appwrite'; import { createSessionClient } from '@/lib/appwrite.server'; +import { Query } from 'node-appwrite'; import Link from 'next/link'; import { Pencil } from 'lucide-react'; import { DeleteButton } from '@/components/DeleteButton'; import { collectionMap, SCHEMA_MAP } from '@/lib/schema'; -export default async function EntityPage({ params }: { params: { entity: string } }) { - // Await the params object before using its properties +export default async function EntityPage({ + params, + searchParams +}: { + params: { entity: string }, + searchParams: { q?: string, page?: string } +}) { const { entity } = await params; + const sParams = await searchParams; + const q = sParams.q || ''; + const page = Number(sParams.page) || 1; + const limit = 15; + const offset = (page - 1) * limit; const collectionId = collectionMap[entity]; const fieldsConfig = SCHEMA_MAP[entity] || []; @@ -31,18 +42,39 @@ export default async function EntityPage({ params }: { params: { entity: string } let documents: any[] = []; + let total = 0; try { const sessionClient = await createSessionClient(); if (!sessionClient) { throw new Error("No sesión activa"); } + const queries = [ + Query.limit(limit), + Query.offset(offset), + Query.orderDesc('$createdAt') + ]; + + if (q) { + const searchableFields = fieldsConfig + .filter(f => f.type === 'text' || f.type === 'textarea') + .map(f => f.name) + .slice(0, 2); // Limit to first two text fields for performance + + if (searchableFields.length > 0) { + // Note: Appwrite multiple contains are AND-ed by default. + // For a better search we might need something else but this is a good start. + queries.push(Query.contains(searchableFields[0], q)); + } + } + const response = await sessionClient.databases.listDocuments( APPWRITE_ALMEIRA_CONFIG.databaseId, collectionId, - [] + queries ); documents = response.documents; + total = response.total; } catch (error) { console.error(`Error fetching from ${collectionId}:`, error); } @@ -60,16 +92,30 @@ export default async function EntityPage({ params }: { params: { entity: string }; return ( -
-
+
+

{entityName}

- Total almacenado: {documents.length} + Total en sistema: {total}

+ +
+ + +
+ + Nuevo @@ -78,8 +124,8 @@ export default async function EntityPage({ params }: { params: { entity: string
{documents.length === 0 ? (
-

AÚN NO HAY DATOS

- Para comenzar, haz clic en el botón superior derecho llamado "Añadir Nuevo". +

NO SE ENCONTRARON REGISTROS

+ Prueba ajustando tu búsqueda o crea un nuevo elemento.
) : (
@@ -99,9 +145,17 @@ export default async function EntityPage({ params }: { params: { entity: string {rawHeaders.map(h => { let value = doc[h]; + const field = fieldsConfig.find(f => f.name === h); + + // Resolve Relations if (typeof value === 'object' && value !== null) { - value = Array.isArray(value) ? `[Lista (${value.length})]` : value.$id || '[Vinculado]'; + if (field?.type === 'relation' && field.relationLabel) { + value = value[field.relationLabel] || value.$id || '[Vinculado]'; + } else { + value = Array.isArray(value) ? `[Lista (${value.length})]` : value.$id || '[Vinculado]'; + } } + if (typeof value === 'boolean') { value = value ? 'Sí' : 'No'; } @@ -128,6 +182,27 @@ export default async function EntityPage({ params }: { params: { entity: string
)}
+ + {/* Pagination */} + {total > limit && ( +
+ + ANTERIOR + + + Página {page} de {Math.ceil(total / limit)} + + = Math.ceil(total / limit) ? 'opacity-30 pointer-events-none bg-slate-900' : 'bg-slate-900 text-white hover:border-cyan-500'}`} + > + SIGUIENTE + +
+ )}
); } diff --git a/src/app/dashboard/media/page.tsx b/src/app/dashboard/media/page.tsx index 5fed7a5..91614d2 100644 --- a/src/app/dashboard/media/page.tsx +++ b/src/app/dashboard/media/page.tsx @@ -4,18 +4,42 @@ import Link from 'next/link'; import { Image as ImageIcon, Video, Plus, Trash2 } from 'lucide-react'; import { DeleteButton } from '@/components/DeleteButton'; -export default async function MediaLibraryPage() { +import { Query } from 'node-appwrite'; + +export default async function MediaLibraryPage({ + searchParams +}: { + searchParams: { q?: string, page?: string } +}) { + const sParams = await searchParams; + const q = sParams.q || ''; + const page = Number(sParams.page) || 1; + const limit = 12; + const offset = (page - 1) * limit; + let media: any[] = []; + let total = 0; try { const sessionClient = await createSessionClient(); if (!sessionClient) throw new Error("No sesión activa"); + const queries = [ + Query.limit(limit), + Query.offset(offset), + Query.orderDesc('$createdAt') + ]; + + if (q) { + queries.push(Query.contains('titulo', q)); + } + const response = await sessionClient.databases.listDocuments( APPWRITE_ALMEIRA_CONFIG.databaseId, 'media', - [] + queries ); media = response.documents; + total = response.total; } catch (error) { console.error(`Error fetching media:`, error); } @@ -31,15 +55,29 @@ export default async function MediaLibraryPage() { return (
-
+

Biblioteca de Medios

- Total almacenado: {media.length} + Total en sistema: {total}

+ +
+ + +
+ Subir Archivo @@ -49,54 +87,77 @@ export default async function MediaLibraryPage() { {media.length === 0 ? (
-

BIBLIOTECA VACÍA

- Para comenzar, sube y optimiza tu primer recurso multimedia con el botón superior. +

NO SE ENCONTRARON RECURSOS

+ Prueba ajustando tu búsqueda o sube un nuevo archivo.
) : ( -
- {media.map((item) => { - const fileUrl = getFilePreviewUrl(item.bucket_id, item.file_id); - return ( -
-
- {item.tipo === 'video' ? ( - <> -
+ ); + })} +
+ + {/* Pagination */} + {total > limit && ( +
+ + ANTERIOR + + + Página {page} de {Math.ceil(total / limit)} + + = Math.ceil(total / limit) ? 'opacity-30 pointer-events-none bg-slate-900' : 'bg-slate-900 text-white hover:border-pink-500'}`} + > + SIGUIENTE + +
+ )} + )}
);