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 React, { useRef, useState, useTransition } from 'react'
|
||||||
import { useRouter } from 'next/navigation'
|
import { useRouter } from 'next/navigation'
|
||||||
|
|
||||||
import { createPastor, deleteLetter, updatePastor, uploadLetter } from './actions'
|
import { createPastor, deleteLetter, deletePastor, updatePastor, uploadLetter } from './actions'
|
||||||
|
|
||||||
export type PastorRow = {
|
export type PastorRow = {
|
||||||
id: number
|
id: number
|
||||||
|
|
@ -243,23 +243,7 @@ export function PastorsTable({ rows }: { rows: PastorRow[] }) {
|
||||||
</td>
|
</td>
|
||||||
))}
|
))}
|
||||||
<td className="text-right">
|
<td className="text-right">
|
||||||
<button
|
<RowActions pastor={p} onEdit={() => setEditing(p)} />
|
||||||
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>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</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 }) {
|
function LetterActions({ pastor, onUpload }: { pastor: PastorRow; onUpload: () => void }) {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const [pending, startTransition] = useTransition()
|
const [pending, startTransition] = useTransition()
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,31 @@ export async function updatePastor(id: number, data: PastorUpdateInput) {
|
||||||
return { ok: true }
|
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
|
* 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.
|
* el documento de subida, de modo que se pueda subir uno nuevo en su lugar.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue