diff --git a/src/app/dashboard/[entity]/page.tsx b/src/app/dashboard/[entity]/page.tsx
index 0026317..f7b2f79 100644
--- a/src/app/dashboard/[entity]/page.tsx
+++ b/src/app/dashboard/[entity]/page.tsx
@@ -15,13 +15,14 @@ export default async function EntityPage({
searchParams,
}: {
params: Promise<{ entity: string }>;
- searchParams: Promise<{ q?: string; page?: string; sort?: string; dir?: string }>;
+ searchParams: Promise<{ q?: string; page?: string; sort?: string; dir?: string; year?: string; month?: string; status?: string }>;
}) {
const { entity } = await params;
const sParams = await searchParams;
const q = sParams.q || '';
const page = Number(sParams.page) || 1;
- const sort = sParams.sort || '$createdAt';
+ const defaultSort = entity === 'posts' ? 'fecha_publicacion' : '$createdAt';
+ const sort = sParams.sort || defaultSort;
const dir: 'asc' | 'desc' = sParams.dir === 'asc' ? 'asc' : 'desc';
const limit: number = entity === 'posts' ? 12 : 15;
const offset = (page - 1) * limit;
@@ -73,46 +74,81 @@ export default async function EntityPage({
}
}
+ if (entity === 'posts') {
+ const status = sParams.status || '';
+ if (status && status !== 'all') {
+ if (status === 'publicado') {
+ queries.push(Query.equal('estado', ['publicado', 'Publicado']));
+ } else if (status === 'borrador') {
+ queries.push(Query.equal('estado', ['borrador', 'Borrador']));
+ } else if (status === 'pendiente') {
+ queries.push(Query.equal('estado', ['pendiente', 'Pendiente']));
+ } else if (status === 'archivado') {
+ queries.push(Query.equal('estado', ['archivado', 'Archivado']));
+ } else {
+ queries.push(Query.equal('estado', status));
+ }
+ }
+
+ const year = sParams.year ? Number(sParams.year) : null;
+ const month = sParams.month ? Number(sParams.month) : null;
+
+ if (year || month) {
+ const filterYear = year || new Date().getFullYear();
+ if (month) {
+ const startDate = new Date(Date.UTC(filterYear, month - 1, 1, 0, 0, 0)).toISOString();
+ const endDate = new Date(Date.UTC(filterYear, month, 1, 0, 0, 0)).toISOString();
+ queries.push(Query.greaterThanEqual('fecha_publicacion', startDate));
+ queries.push(Query.lessThan('fecha_publicacion', endDate));
+ } else {
+ const startDate = new Date(Date.UTC(filterYear, 0, 1, 0, 0, 0)).toISOString();
+ const endDate = new Date(Date.UTC(filterYear + 1, 0, 1, 0, 0, 0)).toISOString();
+ queries.push(Query.greaterThanEqual('fecha_publicacion', startDate));
+ queries.push(Query.lessThan('fecha_publicacion', endDate));
+ }
+ }
+ }
+
const response = await client.databases.listDocuments(
APPWRITE_ALMEIRA_CONFIG.databaseId,
collectionId,
queries
);
-
+
documents = JSON.parse(JSON.stringify(response.documents));
total = response.total;
const fields = SCHEMA_MAP[entity] || [];
- await Promise.all(
- fields
- .filter((f) => f.type === 'relation' && f.relationCollection)
- .map(async (field) => {
- const relKey = field.relationCollection as string;
- const relColId = collectionMap[relKey];
- if (!relColId) return;
+ await Promise.all(
+ fields
+ .filter((f) => f.type === 'relation' && f.relationCollection)
+ .map(async (field) => {
+ const relKey = field.relationCollection as string;
+ const relColId = collectionMap[relKey];
+ if (!relColId) return;
- const ids = Array.from(new Set(
- response.documents
- .map(d => typeof d[field.name] === 'object' && d[field.name] !== null ? d[field.name].$id : d[field.name])
- .filter(id => typeof id === 'string' && id.length > 0) as string[]
- ));
+ const ids = Array.from(new Set(
+ response.documents
+ .map(d => typeof d[field.name] === 'object' && d[field.name] !== null ? d[field.name].$id : d[field.name])
+ .filter(id => typeof id === 'string' && id.length > 0) as string[]
+ ));
- if (ids.length === 0) {
- relationData[field.name] = [];
- return;
- }
+ if (ids.length === 0) {
+ relationData[field.name] = [];
+ return;
+ }
+
+ try {
+ const relRes = await client.databases.listDocuments(
+ APPWRITE_ALMEIRA_CONFIG.databaseId,
+ relColId,
+ [Query.equal('$id', ids), Query.limit(100)]
+ );
+ relationData[field.name] = JSON.parse(JSON.stringify(relRes.documents));
+ } catch { }
+ })
+ );
- try {
- const relRes = await client.databases.listDocuments(
- APPWRITE_ALMEIRA_CONFIG.databaseId,
- relColId,
- [Query.equal('$id', ids), Query.limit(100)]
- );
- relationData[field.name] = JSON.parse(JSON.stringify(relRes.documents));
- } catch { }
- })
- );
-
// Enrich documents with resolved relation data for DataTable
documents = documents.map(doc => {
const enriched = { ...doc };
@@ -137,6 +173,18 @@ export default async function EntityPage({
return updateEntityAction(entity, id, formData);
};
+ const buildQueryString = (p: number) => {
+ const paramsObj = new URLSearchParams();
+ paramsObj.set('page', p.toString());
+ if (q) paramsObj.set('q', q);
+ if (sort) paramsObj.set('sort', sort);
+ if (dir) paramsObj.set('dir', dir);
+ if (sParams.year) paramsObj.set('year', sParams.year);
+ if (sParams.month) paramsObj.set('month', sParams.month);
+ if (sParams.status) paramsObj.set('status', sParams.status);
+ return paramsObj.toString();
+ };
+
return (
@@ -149,7 +197,7 @@ export default async function EntityPage({
-
@@ -173,7 +226,7 @@ export default async function EntityPage({
{entity === 'posts' ? (
) : (
@@ -204,31 +262,31 @@ export default async function EntityPage({
return (
« INICIO
ANTERIOR
-
+
Página {page} de {totalPages}
-
+
= totalPages ? 'opacity-30 pointer-events-none bg-slate-900' : 'bg-slate-900 text-white hover:border-cyan-500'}`}
>
SIGUIENTE
= totalPages ? 'opacity-30 pointer-events-none bg-slate-900' : 'bg-slate-900 text-white hover:border-cyan-500'}`}
title="Ir a la última página"
>
diff --git a/src/components/publications/PublicationsList.tsx b/src/components/publications/PublicationsList.tsx
index 92ae738..9c6ac52 100644
--- a/src/components/publications/PublicationsList.tsx
+++ b/src/components/publications/PublicationsList.tsx
@@ -20,7 +20,7 @@
import React, { useState, useCallback, useMemo, useTransition } from 'react';
import { useRouter } from 'next/navigation';
-import { Search, Plus, Filter, Loader2, FileText } from 'lucide-react';
+import { Search, Plus, Filter, Loader2, FileText, Calendar, ArrowUpDown, ArrowUp, ArrowDown } from 'lucide-react';
import Link from 'next/link';
import type { Post, AuthUser, UserRole } from '@/types/publications';
import PostCard from './PostCard';
@@ -43,10 +43,13 @@ interface PublicationsListProps {
};
globalSettings?: Record
;
entity: string;
+ currentSort?: string;
+ currentDir?: 'asc' | 'desc';
+ currentYear?: string;
+ currentMonth?: string;
+ currentStatus?: string;
}
-type SortKey = 'fecha_publicacion' | '$createdAt' | 'titulo' | 'estado';
-
// ─────────────────────────────────────────────
// COMPONENT
// ─────────────────────────────────────────────
@@ -59,6 +62,11 @@ export const PublicationsList: React.FC = ({
appwriteConfig,
globalSettings,
entity,
+ currentSort = 'fecha_publicacion',
+ currentDir = 'desc',
+ currentYear = '',
+ currentMonth = '',
+ currentStatus = 'all',
}) => {
const router = useRouter();
const toast = useToast();
@@ -67,40 +75,79 @@ export const PublicationsList: React.FC = ({
// Local state
const [posts, setPosts] = useState(initialPosts);
- const [statusFilter, setStatusFilter] = useState('all');
- const [sortBy, setSortBy] = useState('fecha_publicacion');
-
const canEdit = user?.$id === 'admin-emergency' || user?.labels?.includes('admin') || user?.labels?.includes('editor');
const userRole = canEdit ? 'admin' : 'viewer';
- // ── DERIVED STATE ────────────────────────────
+ // ── FILTER & SORT ACTIONS ─────────────────────
+ const updateFilters = useCallback((updates: {
+ sort?: string;
+ dir?: 'asc' | 'desc';
+ year?: string;
+ month?: string;
+ status?: string;
+ page?: number;
+ }) => {
+ const params = new URLSearchParams(window.location.search);
+
+ // Default page to 1 on filter changes
+ params.set('page', (updates.page ?? 1).toString());
- const filteredAndSortedPosts = useMemo(() => {
- let result = [...posts];
-
-
- // Filter by status
- if (statusFilter !== 'all') {
- result = result.filter((p) => p.estado === statusFilter);
+ if (updates.sort !== undefined) {
+ if (updates.sort) params.set('sort', updates.sort);
+ else params.delete('sort');
+ }
+ if (updates.dir !== undefined) {
+ if (updates.dir) params.set('dir', updates.dir);
+ else params.delete('dir');
+ }
+ if (updates.year !== undefined) {
+ if (updates.year) params.set('year', updates.year);
+ else params.delete('year');
+ }
+ if (updates.month !== undefined) {
+ if (updates.month) params.set('month', updates.month);
+ else params.delete('month');
+ }
+ if (updates.status !== undefined) {
+ if (updates.status && updates.status !== 'all') params.set('status', updates.status);
+ else params.delete('status');
}
- // Sort
- result.sort((a, b) => {
- if (sortBy === 'titulo') return a.titulo.localeCompare(b.titulo);
- if (sortBy === 'estado') return (a.estado || '').localeCompare(b.estado || '');
- const aDate = new Date(a[sortBy] || a.fecha_publicacion || a.$createdAt || 0).getTime();
- const bDate = new Date(b[sortBy] || b.fecha_publicacion || b.$createdAt || 0).getTime();
- return bDate - aDate; // Strictly Newest first
- });
+ router.push(`/dashboard/${entity}?${params.toString()}`);
+ }, [entity, router]);
- return result;
- }, [posts, statusFilter, sortBy]);
+ const years = useMemo(() => {
+ const currentYear = new Date().getFullYear();
+ const startYear = 2024;
+ const list = [];
+ for (let y = currentYear; y >= startYear; y--) {
+ list.push(y);
+ }
+ return list;
+ }, []);
+
+ const months = useMemo(() => [
+ { value: '1', label: 'Enero' },
+ { value: '2', label: 'Febrero' },
+ { value: '3', label: 'Marzo' },
+ { value: '4', label: 'Abril' },
+ { value: '5', label: 'Mayo' },
+ { value: '6', label: 'Junio' },
+ { value: '7', label: 'Julio' },
+ { value: '8', label: 'Agosto' },
+ { value: '9', label: 'Septiembre' },
+ { value: '10', label: 'Octubre' },
+ { value: '11', label: 'Noviembre' },
+ { value: '12', label: 'Diciembre' },
+ ], []);
+
+ const filteredAndSortedPosts = posts;
const stats = useMemo(() => ({
total: posts.length,
- published: posts.filter((p) => p.estado === 'publicado').length,
- draft: posts.filter((p) => p.estado === 'borrador').length,
- pending: posts.filter((p) => p.estado === 'pendiente').length,
+ published: posts.filter((p) => (p.estado || '').toLowerCase() === 'publicado').length,
+ draft: posts.filter((p) => (p.estado || '').toLowerCase() === 'borrador').length,
+ pending: posts.filter((p) => (p.estado || '').toLowerCase() === 'pendiente').length,
}), [posts]);
// ── HANDLERS ────────────────────────────────
@@ -153,50 +200,113 @@ export const PublicationsList: React.FC = ({
{/* Toolbar */}
-
+
+
+ {/* Status Filter */}
+
+
+
+
+ {/* Year Filter */}
+
+
+
+
- {/* Status Filter */}
-
-
-
+ {/* Month Filter */}
+
+
+
+
- {/* Sort */}
-
+
+ {/* Sort By Field */}
+
+
+
+
- {/* New post button */}
-
- updateFilters({ dir: currentDir === 'asc' ? 'desc' : 'asc' })}
+ className="flex items-center gap-2 bg-slate-950/50 border border-white/5 hover:border-cyan-500/50 text-white text-sm px-4 py-2.5 rounded-xl transition-all active:scale-95 cursor-pointer"
+ title={currentDir === 'asc' ? 'Orden Ascendente' : 'Orden Descendente'}
>
-
- Nueva publicación
-
-
+ {currentDir === 'asc' ? (
+ <>
+
+
Ascendente
+ >
+ ) : (
+ <>
+
+
Descendente
+ >
+ )}
+
+
+ {/* Clear Filters Button (only show if any filter is active) */}
+ {(currentStatus !== 'all' || currentYear || currentMonth) && (
+
+ )}
+
+ {/* New post button */}
+
+
+
+ Nueva publicación
+
+
+
{/* Grid de publicaciones */}