feat: version 2.10.0 -- Notas en tarjetas, crear/editar en popup, compartir de solo lectura

Rediseno completo del modulo Notas (era lista inline, sin popup, sin compartir):
- Tarjetas con resumen del contenido en vez de lista vertical; la primera tarjeta siempre es
  un "+" para crear.
- Crear y editar se hacen en un DetailPopup (antes: textarea fijo arriba + edicion inline por
  tarjeta).
- Nueva tabla note_shares (migracion 019): compartir una nota con otro usuario ya registrado,
  siempre de solo lectura -- el dueño sigue siendo el unico que edita/borra/comparte (mismo
  patron que project_members pero sin rol ni invitacion por correo, ya que acá no hace falta).
  Quien recibe la nota la ve con "Compartida por {nombre}", sin ningun control de edicion.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Milton Gonzalez
2026-10-12 12:09:04 -05:00
parent dd8384fc88
commit 926215297e
14 changed files with 278 additions and 90 deletions
+36 -4
View File
@@ -1,11 +1,18 @@
import { pool } from '../../config/db.js';
export async function findByUser(userId) {
// Notas propias MAS las compartidas conmigo. is_owner permite al frontend decidir si se puede
// editar/borrar/compartir (siempre false para una nota compartida: el dueno nunca cambia).
export async function findVisibleToUser(userId) {
const [rows] = await pool.query(
'SELECT id, user_id, content, created_at, updated_at FROM notes WHERE user_id = ? ORDER BY updated_at DESC',
[userId]
`SELECT n.id, n.user_id, n.content, n.created_at, n.updated_at,
(n.user_id = ?) AS is_owner, u.name AS owner_name
FROM notes n
JOIN users u ON u.id = n.user_id
WHERE n.user_id = ? OR n.id IN (SELECT note_id FROM note_shares WHERE user_id = ?)
ORDER BY n.updated_at DESC`,
[userId, userId, userId]
);
return rows;
return rows.map((r) => ({ ...r, is_owner: !!r.is_owner }));
}
export async function findById(id) {
@@ -26,3 +33,28 @@ export async function update(id, content) {
export async function remove(id) {
await pool.query('DELETE FROM notes WHERE id = ?', [id]);
}
export async function listShares(noteId) {
const [rows] = await pool.query(
`SELECT ns.user_id, u.name, u.email, ns.shared_at
FROM note_shares ns JOIN users u ON u.id = ns.user_id
WHERE ns.note_id = ? ORDER BY u.name`,
[noteId]
);
return rows;
}
export async function isSharedWith(noteId, userId) {
const [rows] = await pool.query('SELECT 1 FROM note_shares WHERE note_id = ? AND user_id = ?', [noteId, userId]);
return rows.length > 0;
}
export async function addShare(noteId, userId) {
await pool.query('INSERT IGNORE INTO note_shares (note_id, user_id) VALUES (?, ?)', [noteId, userId]);
return listShares(noteId);
}
export async function removeShare(noteId, userId) {
await pool.query('DELETE FROM note_shares WHERE note_id = ? AND user_id = ?', [noteId, userId]);
return listShares(noteId);
}