Skip to content
114 changes: 102 additions & 12 deletions src/Pages/CardReader/CardReader.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useState, useEffect, useMemo } from 'react';
import { useState, useEffect } from 'react';
import { BASE_API_URL } from '../../Enums';
import { useSCE } from '../../Components/context/SceContext';
import { getAllCardsFromDb, deleteCardFromDb } from '../../APIFunctions/CardReader';
import { getAllCardsFromDb, deleteCardFromDb, editCardAlias } from '../../APIFunctions/CardReader';
import ConfirmationModal from '../../Components/DecisionModal/ConfirmationModal';
import { trashcanSymbol } from '../Overview/SVG';
import { trashcanSymbol, pencilSymbol, checkSymbol, cancelSymbol } from '../Overview/SVG';

const header = [
'Time'.padEnd(29),
Expand All @@ -17,10 +17,13 @@ const header = [
export default function CardReader() {
const { user } = useSCE();
const token = user.token;

const [logs, setLogs] = useState([]);
const [cards, setCards] = useState([]);
const [toggleDelete, setToggleDelete] = useState(false);
const [cardToDelete, setCardToDelete] = useState({});
const [editingCardId, setEditingCardId] = useState(null);
const [editedAlias, setEditedAlias] = useState('');
const [tab, setTab] = useState(() => {
const params = new URLSearchParams(window.location.search);
return params.get('tab') || 'registry';
Expand All @@ -44,9 +47,9 @@ export default function CardReader() {

const getColumnClassName = (columnName) => {
let className = 'px-6 py-3 whitespace-nowrap ';
if(columnName === 'lastVerifiedAt' | columnName === 'registrationDate'){
if (['lastVerifiedAt', 'registrationDate'].includes(columnName)) {
className += 'hidden md:table-cell ';
} else if (columnName === 'verifiedCount'){
} else if (columnName === 'verifiedCount') {
className += 'hidden lg:table-cell';
}
return className;
Expand Down Expand Up @@ -89,12 +92,78 @@ export default function CardReader() {
setCardToDelete(card);
}

function handleEditClick(card) {
if (editingCardId === card._id) {
setEditingCardId(null);
setEditedAlias('');
} else {
setEditingCardId(card._id);
setEditedAlias(card.alias);
}
}
Comment on lines +95 to +103

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

put these both in 1 function like

if editing
  do stuff
  return

// do cancel stuff here


async function handleSaveEdit(cardId) {
if (!editedAlias.trim()) {
return; // Don't save empty alias
}

try {
const response = await editCardAlias(token, cardId, editedAlias.trim());
if (!response.error) {
// Refetch all cards from database to ensure UI matches server reality
await getAllCards();
setEditingCardId(null);
setEditedAlias('');
}
} catch (error) {
setLogs(
(currLogs) => [
'[error] unable to update card alias, check browser logs: \n' + error,
...currLogs,
]
);
}
}

function handleEditKeyDown(key) {
if (key === 'Enter') {
handleSaveEdit(editingCardId);
} else if (key === 'Escape') {
setEditingCardId(null);
setEditedAlias('');
}
}

function renderInputOrAlias(card) {
if (editingCardId === card._id) {
return (
<input
type='text'
value={editedAlias}
onChange={(e) => setEditedAlias(e.target.value)}
onKeyDown={(e) => handleEditKeyDown(e.key)}
className='bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded text-gray-700 dark:text-white font-medium m-0 px-1 py-0 focus:outline-none focus:ring-1 focus:ring-blue-500'
style={{ width: '16ch' }}
autoFocus
/>
);
}
return (
<div style={{ width: '16ch', display: 'flex', justifyContent: 'center', overflow: 'hidden' }}>
<span style={{ textOverflow: 'ellipsis', overflow: 'hidden', whiteSpace: 'nowrap' }}>
{card.alias}
</span>
</div>
);
}

function CardEntry({ card }) {
const isEditing = editingCardId === card._id;
return (
<tr key={card._id} className='bg-white border-b dark:bg-gray-800 dark:border-gray-700'>
<td key='alias' className=''>
<div className='px-6 py-4 font-medium text-gray-700 whitespace-nowrap dark:text-white'>
{card.alias}
{renderInputOrAlias(card)}
</div>
</td>
<td key='createdAt' className='hidden md:table-cell'>
Expand All @@ -113,12 +182,33 @@ export default function CardReader() {
</div>
</td>
<td>
<button
className='p-2 hover:bg-gray-200 dark:hover:bg-white/30 rounded-xl'
onClick={() => handleDeleteClick(card)}
>
{trashcanSymbol('#e64539')}
</button>
<div className='flex space-x-2 w-32'>
<button
className={`p-2 hover:bg-gray-200 dark:hover:bg-white/30 rounded-xl ${!isEditing ? 'invisible' : ''}`}
onClick={() => handleSaveEdit(card._id)}
title='Save changes'
>
{checkSymbol('#22c55e')}
</button>
<button
className='p-2 hover:bg-gray-200 dark:hover:bg-white/30 rounded-xl'
onClick={() => handleEditClick(card)}
title={isEditing ? 'Cancel edit' : 'Edit alias'}
>
{isEditing ? (
cancelSymbol('#ef4444')
) : (
pencilSymbol('#6b7280')
)}
</button>
<button
className='p-2 hover:bg-gray-200 dark:hover:bg-white/30 rounded-xl'
onClick={() => handleDeleteClick(card)}
title='Delete card'
>
{trashcanSymbol('#e64539')}
</button>
</div>
</td>
</tr>
);
Expand Down
26 changes: 26 additions & 0 deletions src/Pages/Overview/SVG.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,32 @@ export function trashcanSymbol(color = 'black') {
);
}

export function pencilSymbol(color = '#6b7280') {
return (
<svg width='20' height='20' viewBox='0 0 24 24' fill='none' stroke={color} strokeWidth='2' strokeLinecap='round' strokeLinejoin='round'>
<path d='M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7' />
<path d='m18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z' />
</svg>
);
}

export function checkSymbol(color = '#22c55e') {
return (
<svg width='20' height='20' viewBox='0 0 24 24' fill='none' stroke={color} strokeWidth='2' strokeLinecap='round' strokeLinejoin='round'>
<polyline points='20,6 9,17 4,12'></polyline>
</svg>
);
}

export function cancelSymbol(color = '#ef4444') {
return (
<svg width='20' height='20' viewBox='0 0 24 24' fill='none' stroke={color} strokeWidth='2' strokeLinecap='round' strokeLinejoin='round'>
<line x1='18' y1='6' x2='6' y2='18'></line>
<line x1='6' y1='6' x2='18' y2='18'></line>
</svg>
);
}

export function copyIcon(className, onClick) {
return (
<svg width="24" height="24" viewBox="0 0 512 512" onClick={onClick}>
Expand Down