Skip to content

Commit

Permalink
frontend: add delete button to entity window, improve styling
Browse files Browse the repository at this point in the history
  • Loading branch information
NickSavage committed Dec 20, 2024
1 parent d7a1044 commit 51f9d10
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 43 deletions.
82 changes: 50 additions & 32 deletions zettelkasten-front/src/components/entities/EditEntityDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ interface EditEntityDialogProps {
isOpen: boolean;
onClose: () => void;
onSuccess: () => void;
onDelete: (entity: Entity) => void;
}

export function EditEntityDialog({ entity, isOpen, onClose, onSuccess }: EditEntityDialogProps) {
export function EditEntityDialog({ entity, isOpen, onClose, onSuccess, onDelete }: EditEntityDialogProps) {
const [name, setName] = useState("");
const [description, setDescription] = useState("");
const [type, setType] = useState("");
Expand Down Expand Up @@ -69,53 +70,55 @@ export function EditEntityDialog({ entity, isOpen, onClose, onSuccess }: EditEnt
};

return (
<Dialog open={isOpen} onClose={onClose} className="fixed inset-0 z-50 flex items-center justify-center">
<Dialog open={isOpen} onClose={onClose} className="fixed inset-0 z-50 flex items-center justify-center p-4">
<div className="fixed inset-0 bg-black bg-opacity-30" aria-hidden="true" />

<Dialog.Panel className="bg-white p-6 rounded-lg max-w-md mx-auto relative">
<Dialog.Title className="text-lg font-semibold mb-4">
Edit Entity
</Dialog.Title>

<form onSubmit={handleSubmit}>
<div className="space-y-4">
<Dialog.Panel className="bg-white rounded-xl shadow-xl w-full max-w-2xl mx-auto relative">
<div className="px-6 py-4 border-b border-gray-200">
<Dialog.Title className="text-xl font-semibold text-gray-900">
Edit Entity
</Dialog.Title>
</div>

<form onSubmit={handleSubmit} className="p-6">
<div className="space-y-6">
<div>
<label htmlFor="name" className="block text-sm font-medium text-gray-700">
<label htmlFor="name" className="block text-sm font-medium text-gray-700 mb-1">
Name *
</label>
<input
type="text"
id="name"
value={name}
onChange={(e) => setName(e.target.value)}
className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
className="w-full rounded-lg border border-gray-300 px-4 py-2 focus:border-blue-500 focus:ring-2 focus:ring-blue-500 focus:ring-opacity-30 transition-colors"
required
/>
</div>

<div>
<label htmlFor="description" className="block text-sm font-medium text-gray-700">
<label htmlFor="description" className="block text-sm font-medium text-gray-700 mb-1">
Description
</label>
<textarea
id="description"
value={description}
onChange={(e) => setDescription(e.target.value)}
rows={3}
className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
rows={4}
className="w-full rounded-lg border border-gray-300 px-4 py-2 focus:border-blue-500 focus:ring-2 focus:ring-blue-500 focus:ring-opacity-30 transition-colors"
/>
</div>

<div>
<label htmlFor="type" className="block text-sm font-medium text-gray-700">
<label htmlFor="type" className="block text-sm font-medium text-gray-700 mb-1">
Type
</label>
<input
type="text"
id="type"
value={type}
onChange={(e) => setType(e.target.value)}
className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
className="w-full rounded-lg border border-gray-300 px-4 py-2 focus:border-blue-500 focus:ring-2 focus:ring-blue-500 focus:ring-opacity-30 transition-colors"
/>
</div>

Expand All @@ -124,12 +127,12 @@ export function EditEntityDialog({ entity, isOpen, onClose, onSuccess }: EditEnt
Linked Card
</label>
{linkedCard ? (
<div className="flex items-center gap-2">
<div className="flex items-center gap-2 bg-gray-50 p-2 rounded-lg">
<CardTag card={linkedCard} showTitle={true} />
<button
type="button"
onClick={handleRemoveCard}
className="text-red-600 hover:text-red-800"
className="text-red-600 hover:text-red-800 p-1 hover:bg-red-50 rounded-full transition-colors"
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clipRule="evenodd" />
Expand All @@ -144,35 +147,50 @@ export function EditEntityDialog({ entity, isOpen, onClose, onSuccess }: EditEnt
<button
type="button"
onClick={() => setShowCardLink(true)}
className="text-blue-600 hover:text-blue-800"
className="text-blue-600 hover:text-blue-800 flex items-center gap-1"
>
+ Link Card
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z" clipRule="evenodd" />
</svg>
Link Card
</button>
)}
</div>
)}
</div>

{error && (
<div className="text-red-600 text-sm">{error}</div>
<div className="text-red-600 text-sm bg-red-50 p-3 rounded-lg">
{error}
</div>
)}
</div>

<div className="mt-6 flex justify-end gap-4">
<div className="mt-8 pt-6 border-t border-gray-200 flex justify-between">
<button
type="button"
onClick={onClose}
className="px-4 py-2 text-gray-600 hover:text-gray-800"
>
Cancel
</button>
<button
type="submit"
disabled={isSubmitting || !name.trim()}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 disabled:opacity-50"
onClick={() => entity && onDelete(entity)}
className="px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-opacity-50"
>
{isSubmitting ? "Saving..." : "Save"}
Delete Entity
</button>

<div className="flex gap-4">
<button
type="button"
onClick={onClose}
className="px-4 py-2 text-gray-700 hover:text-gray-900 transition-colors"
>
Cancel
</button>
<button
type="submit"
disabled={isSubmitting || !name.trim()}
className="px-6 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50"
>
{isSubmitting ? "Saving..." : "Save"}
</button>
</div>
</div>
</form>
</Dialog.Panel>
Expand Down
41 changes: 30 additions & 11 deletions zettelkasten-front/src/components/entities/EntityList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export function EntityList() {
const [editingEntity, setEditingEntity] = useState<Entity | null>(null);
const [showEditDialog, setShowEditDialog] = useState(false);
const [selectionMode, setSelectionMode] = useState(false);
const [entityToDelete, setEntityToDelete] = useState<Entity | null>(null);
const navigate = useNavigate();

const loadEntities = () => {
Expand Down Expand Up @@ -106,16 +107,21 @@ export function EntityList() {
};

const handleConfirmDelete = async () => {
if (selectedEntities.length === 0) return;
const entitiesToDelete = entityToDelete
? [entityToDelete.id]
: selectedEntities;

if (entitiesToDelete.length === 0) return;

setShowDeleteDialog(false);
setIsDeleting(true);

try {
// Delete all selected entities
for (const entityId of selectedEntities) {
for (const entityId of entitiesToDelete) {
await deleteEntity(entityId);
}
setSelectedEntities([]);
setEntityToDelete(null);
loadEntities();
} catch (err) {
setError("Failed to delete entities");
Expand Down Expand Up @@ -168,6 +174,13 @@ export function EntityList() {
}
};

const handleSingleDelete = (entity: Entity) => {
setEditingEntity(null);
setShowEditDialog(false);
setEntityToDelete(entity);
setShowDeleteDialog(true);
};

if (loading) return <div className="p-4">Loading entities...</div>;
if (error) return <div className="p-4 text-red-600">{error}</div>;

Expand Down Expand Up @@ -305,10 +318,13 @@ export function EntityList() {
</Dialog>
)}

{showDeleteDialog && selectedEntities.length > 0 && (
{showDeleteDialog && (
<Dialog
open={showDeleteDialog}
onClose={() => setShowDeleteDialog(false)}
onClose={() => {
setShowDeleteDialog(false);
setEntityToDelete(null);
}}
className="fixed inset-0 z-50 flex items-center justify-center"
>
<div className="fixed inset-0 bg-black bg-opacity-30" aria-hidden="true" />
Expand All @@ -318,25 +334,27 @@ export function EntityList() {
</Dialog.Title>
<div className="mb-4">
<p className="text-gray-600 mb-2">
Are you sure you want to delete {selectedEntities.length === 1 ? 'this entity' : 'these entities'}?
Are you sure you want to delete {entityToDelete ? 'this entity' : 'these entities'}?
</p>
<ul className="list-disc pl-5">
{selectedEntities.map((id) => {
const entity = entities.find((e) => e.id === id);
{(entityToDelete ? [entityToDelete] : selectedEntities.map(id => entities.find(e => e.id === id))).map((entity) => {
return entity ? (
<li key={id} className="text-gray-700">
<li key={entity.id} className="text-gray-700">
{entity.name} ({entity.type})
</li>
) : null;
})}
</ul>
</div>
<p className="text-red-600 text-sm mb-4">
This action cannot be undone. {selectedEntities.length === 1 ? 'The entity' : 'These entities'} will be permanently deleted.
This action cannot be undone. {entityToDelete ? 'The entity' : 'These entities'} will be permanently deleted.
</p>
<div className="flex justify-end gap-4">
<button
onClick={() => setShowDeleteDialog(false)}
onClick={() => {
setShowDeleteDialog(false);
setEntityToDelete(null);
}}
className="px-4 py-2 text-gray-600 hover:text-gray-800"
>
Cancel
Expand All @@ -360,6 +378,7 @@ export function EntityList() {
setEditingEntity(null);
}}
onSuccess={handleEditSuccess}
onDelete={handleSingleDelete}
/>
</div>
);
Expand Down

0 comments on commit 51f9d10

Please sign in to comment.