Merge row edit/delete into a dropdown menu
- New deletePastor server action (also removes the pastor's letter to avoid orphaned uploads) - Replace the per-row edit icon with a "⋮" actions dropdown containing Editar and Eliminar (Eliminar confirms before deleting) - Uses the popover API + CSS anchor positioning so the menu isn't clipped by the table's horizontal-scroll container Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
ba08fc087b
commit
b840172946
|
|
@ -3,7 +3,7 @@
|
|||
import React, { useRef, useState, useTransition } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
|
||||
import { createPastor, deleteLetter, updatePastor, uploadLetter } from './actions'
|
||||
import { createPastor, deleteLetter, deletePastor, updatePastor, uploadLetter } from './actions'
|
||||
|
||||
export type PastorRow = {
|
||||
id: number
|
||||
|
|
@ -243,23 +243,7 @@ export function PastorsTable({ rows }: { rows: PastorRow[] }) {
|
|||
</td>
|
||||
))}
|
||||
<td className="text-right">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-xs btn-square btn-ghost"
|
||||
onClick={() => setEditing(p)}
|
||||
aria-label={`Editar ${p.name}`}
|
||||
title="Editar"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="w-4 h-4"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" />
|
||||
</svg>
|
||||
</button>
|
||||
<RowActions pastor={p} onEdit={() => setEditing(p)} />
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
|
|
@ -283,6 +267,96 @@ export function PastorsTable({ rows }: { rows: PastorRow[] }) {
|
|||
)
|
||||
}
|
||||
|
||||
function RowActions({ pastor, onEdit }: { pastor: PastorRow; onEdit: () => void }) {
|
||||
const router = useRouter()
|
||||
const [pending, startTransition] = useTransition()
|
||||
const menuId = `row-menu-${pastor.id}`
|
||||
const anchor = `--row-anchor-${pastor.id}`
|
||||
|
||||
const close = () => document.getElementById(menuId)?.hidePopover?.()
|
||||
|
||||
const onDelete = () => {
|
||||
close()
|
||||
if (!window.confirm(`¿Eliminar a ${pastor.name}? Esta acción no se puede deshacer.`)) return
|
||||
startTransition(async () => {
|
||||
await deletePastor(pastor.id)
|
||||
router.refresh()
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-xs btn-square btn-ghost"
|
||||
popoverTarget={menuId}
|
||||
style={{ anchorName: anchor } as React.CSSProperties}
|
||||
aria-label={`Acciones para ${pastor.name}`}
|
||||
disabled={pending}
|
||||
>
|
||||
{pending ? (
|
||||
<span className="loading loading-spinner loading-xs" />
|
||||
) : (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="w-4 h-4"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M10 6a2 2 0 110-4 2 2 0 010 4zm0 6a2 2 0 110-4 2 2 0 010 4zm0 6a2 2 0 110-4 2 2 0 010 4z" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
<ul
|
||||
id={menuId}
|
||||
popover="auto"
|
||||
style={{ positionAnchor: anchor } as React.CSSProperties}
|
||||
className="dropdown dropdown-end menu w-40 rounded-box bg-base-100 p-2 shadow-lg border border-base-300"
|
||||
>
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
close()
|
||||
onEdit()
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="w-4 h-4"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" />
|
||||
</svg>
|
||||
Editar
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button" className="text-error" onClick={onDelete}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="w-4 h-4"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
Eliminar
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function LetterActions({ pastor, onUpload }: { pastor: PastorRow; onUpload: () => void }) {
|
||||
const router = useRouter()
|
||||
const [pending, startTransition] = useTransition()
|
||||
|
|
|
|||
|
|
@ -88,6 +88,31 @@ export async function updatePastor(id: number, data: PastorUpdateInput) {
|
|||
return { ok: true }
|
||||
}
|
||||
|
||||
/**
|
||||
* Elimina un pastor por completo. También borra su carta asociada (si existe)
|
||||
* para no dejar archivos huérfanos.
|
||||
*/
|
||||
export async function deletePastor(id: number) {
|
||||
const payload = await getPayload({ config: await config })
|
||||
|
||||
const pastor = await payload.findByID({ collection: 'pastors', id, depth: 0 })
|
||||
const letterId =
|
||||
pastor.letter && typeof pastor.letter === 'object' ? pastor.letter.id : pastor.letter
|
||||
|
||||
await payload.delete({ collection: 'pastors', id })
|
||||
|
||||
if (letterId) {
|
||||
try {
|
||||
await payload.delete({ collection: 'pastor-letters', id: letterId })
|
||||
} catch {
|
||||
// The upload doc may already be gone; deleting the pastor is enough.
|
||||
}
|
||||
}
|
||||
|
||||
revalidatePath('/')
|
||||
return { ok: true }
|
||||
}
|
||||
|
||||
/**
|
||||
* Elimina la carta asociada a un pastor: desvincula el campo `letter` y borra
|
||||
* el documento de subida, de modo que se pueda subir uno nuevo en su lugar.
|
||||
|
|
|
|||
Loading…
Reference in New Issue