refactor(assignee-selector, kanban-group): clean up code formatting and improve modal confirmation handling

- Removed unnecessary line breaks and improved code readability in AssigneeSelector and KanbanGroup components.
- Refactored modal confirmation logic in KanbanGroup to streamline delete operations.
- Enhanced the dropdown and member list rendering in AssigneeSelector for better performance and user experience.
This commit is contained in:
chamikaJ
2025-07-30 13:09:43 +05:30
parent 9da6dced01
commit d9700a9b2c
2 changed files with 130 additions and 311 deletions

View File

@@ -27,7 +27,7 @@ const AssigneeSelector: React.FC<AssigneeSelectorProps> = ({
task, task,
groupId = null, groupId = null,
isDarkMode = false, isDarkMode = false,
kanbanMode = false, kanbanMode = false
}) => { }) => {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [searchQuery, setSearchQuery] = useState(''); const [searchQuery, setSearchQuery] = useState('');
@@ -182,9 +182,12 @@ const AssigneeSelector: React.FC<AssigneeSelectorProps> = ({
// Emit socket event - the socket handler will update Redux with proper types // Emit socket event - the socket handler will update Redux with proper types
socket?.emit(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), JSON.stringify(body)); socket?.emit(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), JSON.stringify(body));
socket?.once(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), (data: any) => { socket?.once(
SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(),
(data: any) => {
dispatch(updateEnhancedKanbanTaskAssignees(data)); dispatch(updateEnhancedKanbanTaskAssignees(data));
}); }
);
// Remove from pending changes after a short delay (optimistic) // Remove from pending changes after a short delay (optimistic)
setTimeout(() => { setTimeout(() => {
@@ -232,8 +235,7 @@ const AssigneeSelector: React.FC<AssigneeSelectorProps> = ({
<PlusOutlined className="text-xs" /> <PlusOutlined className="text-xs" />
</button> </button>
{isOpen && {isOpen && createPortal(
createPortal(
<div <div
ref={dropdownRef} ref={dropdownRef}
onClick={e => e.stopPropagation()} onClick={e => e.stopPropagation()}
@@ -255,12 +257,11 @@ const AssigneeSelector: React.FC<AssigneeSelectorProps> = ({
ref={searchInputRef} ref={searchInputRef}
type="text" type="text"
value={searchQuery} value={searchQuery}
onChange={e => setSearchQuery(e.target.value)} onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Search members..." placeholder="Search members..."
className={` className={`
w-full px-2 py-1 text-xs rounded border w-full px-2 py-1 text-xs rounded border
${ ${isDarkMode
isDarkMode
? 'bg-gray-700 border-gray-600 text-gray-100 placeholder-gray-400 focus:border-blue-500' ? 'bg-gray-700 border-gray-600 text-gray-100 placeholder-gray-400 focus:border-blue-500'
: 'bg-white border-gray-300 text-gray-900 placeholder-gray-500 focus:border-blue-500' : 'bg-white border-gray-300 text-gray-900 placeholder-gray-500 focus:border-blue-500'
} }
@@ -272,7 +273,7 @@ const AssigneeSelector: React.FC<AssigneeSelectorProps> = ({
{/* Members List */} {/* Members List */}
<div className="max-h-48 overflow-y-auto"> <div className="max-h-48 overflow-y-auto">
{filteredMembers && filteredMembers.length > 0 ? ( {filteredMembers && filteredMembers.length > 0 ? (
filteredMembers.map(member => ( filteredMembers.map((member) => (
<div <div
key={member.id} key={member.id}
className={` className={`
@@ -330,6 +331,15 @@ const AssigneeSelector: React.FC<AssigneeSelectorProps> = ({
<span className="text-red-400 ml-1">(Pending)</span> <span className="text-red-400 ml-1">(Pending)</span>
)} )}
</div> </div>
</div>
</div>
))
) : (
<div className={`p-4 text-center ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}`}>
<div className="text-xs">No members found</div>
</div>
)}
</div>
{/* Footer */} {/* Footer */}

View File

@@ -301,35 +301,6 @@ const KanbanGroup: React.FC<KanbanGroupProps> = memo(
}} }}
/> />
{/* Content layer - z-index 1 */}
<div style={{ position: 'relative', zIndex: 1 }}>
<div
className="enhanced-kanban-group-header"
style={{
backgroundColor: headerBackgroundColor,
}}
draggable
onDragStart={e => onGroupDragStart(e, group.id)}
onDragOver={onGroupDragOver}
onDrop={e => onGroupDrop(e, group.id)}
onDragEnd={onDragEnd}
>
<div
className="flex items-center justify-between w-full font-semibold rounded-md"
onMouseEnter={() => setIsHover(true)}
onMouseLeave={() => setIsHover(false)}
>
<div
className="flex items-center gap-2 cursor-pointer"
onClick={e => {
e.stopPropagation();
if ((isProjectManager || isOwnerOrAdmin) && group.name !== t('unmapped'))
setIsEditable(true);
}}
onDragOver={e => { e.preventDefault(); onTaskDragOver(e, group.id, null); }}
onDrop={e => { e.preventDefault(); onTaskDrop(e, group.id, null); }}
/>
{/* Content layer - z-index 1 */} {/* Content layer - z-index 1 */}
<div style={{ position: 'relative', zIndex: 1 }}> <div style={{ position: 'relative', zIndex: 1 }}>
<div <div
@@ -366,7 +337,8 @@ const KanbanGroup: React.FC<KanbanGroupProps> = memo(
<input <input
ref={inputRef} ref={inputRef}
value={name} value={name}
className={`bg-transparent border-none outline-none text-sm font-semibold capitalize min-w-[185px] ${themeMode === 'dark' ? 'text-gray-800' : 'text-gray-900' className={`bg-transparent border-none outline-none text-sm font-semibold capitalize min-w-[185px] ${
themeMode === 'dark' ? 'text-gray-800' : 'text-gray-900'
}`} }`}
onChange={handleChange} onChange={handleChange}
onBlur={handleBlur} onBlur={handleBlur}
@@ -380,7 +352,8 @@ const KanbanGroup: React.FC<KanbanGroupProps> = memo(
/> />
) : ( ) : (
<div <div
className={`min-w-[185px] text-sm font-semibold capitalize truncate ${themeMode === 'dark' ? 'text-gray-800' : 'text-gray-900' className={`min-w-[185px] text-sm font-semibold capitalize truncate ${
themeMode === 'dark' ? 'text-gray-800' : 'text-gray-900'
}`} }`}
title={isEllipsisActive ? name : undefined} title={isEllipsisActive ? name : undefined}
onMouseDown={e => { onMouseDown={e => {
@@ -399,108 +372,6 @@ const KanbanGroup: React.FC<KanbanGroupProps> = memo(
)} )}
</div> </div>
<div className="flex items-center gap-1">
<button
type="button"
className="w-7 h-7 flex items-center justify-center rounded-full hover:bg-black/10 transition-colors"
onClick={() => {
setShowNewCardTop(true);
setShowNewCardBottom(false);
}}
>
<svg className="w-4 h-4 text-gray-800" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
</svg>
</button>
{(isOwnerOrAdmin || isProjectManager) && name !== t('unmapped') && (
<div className="relative" ref={dropdownRef}>
<button
type="button"
className="w-7 h-7 flex items-center justify-center rounded-full hover:bg-black/10 transition-colors"
onClick={() => setShowDropdown(!showDropdown)}
>
<svg className="w-4 h-4 text-gray-800 rotate-90" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 5v.01M12 12v.01M12 19v.01M12 6a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2z" />
</svg>
</button>
{showDropdown && (
<div className="absolute right-0 top-full mt-1 w-48 bg-white dark:bg-gray-800 rounded-md shadow-lg border border-gray-200 dark:border-gray-700 z-50">
<div className="py-1">
<button
type="button"
className="w-full px-4 py-2 text-left text-sm hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center gap-2"
onClick={handleRename}
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
{t('rename')}
</button>
{groupBy === IGroupBy.STATUS && statusCategories && (
<div className="border-t border-gray-200 dark:border-gray-700">
<div className="px-4 py-2 text-xs font-medium text-gray-500 uppercase tracking-wide">
{t('changeCategory')}
</div>
{statusCategories.map(status => (
<button
key={status.id}
type="button"
className="w-full px-4 py-2 text-left text-sm hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center gap-2"
onClick={() => status.id && handleCategoryChange(status.id)}
>
<div
className="w-3 h-3 rounded-full"
style={{ backgroundColor: status.color_code }}
></div>
<span className={group.category_id === status.id ? 'font-bold' : ''}>
{status.name}
</span>
</button>
))}
</div>
)}
{groupBy !== IGroupBy.PRIORITY && (
<div className="border-t border-gray-200 dark:border-gray-700">
<button
type="button"
className="w-full px-4 py-2 text-left text-sm hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center gap-2 text-red-600 dark:text-red-400"
onClick={e => {
e.stopPropagation();
handleDelete();
}}
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
{t('delete')}
</button>
</div>
)}
</div>
</div>
)}
</div>
)}
</div>
</div>
</div>
{/* Simple Delete Confirmation */}
{/* Portal-based confirmation removed, now handled by Modal.confirm */}
<div className="enhanced-kanban-group-tasks">
{/* Create card at top */}
{showNewCardTop && (
<EnhancedKanbanCreateTaskCard
sectionId={group.id}
setShowNewCard={setShowNewCardTop}
position="top"
/>
)}
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<button <button
type="button" type="button"
@@ -634,69 +505,7 @@ const KanbanGroup: React.FC<KanbanGroupProps> = memo(
</div> </div>
{/* Simple Delete Confirmation */} {/* Simple Delete Confirmation */}
{showDeleteConfirm && ( {/* Portal-based confirmation removed, now handled by Modal.confirm */}
<Portal>
<div
className="fixed inset-0 bg-black bg-opacity-25 flex items-center justify-center z-[99999]"
onClick={() => setShowDeleteConfirm(false)}
>
<div
className="bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 max-w-sm w-full mx-4"
onClick={e => e.stopPropagation()}
>
<div className="p-4">
<div className="flex items-center gap-3 mb-3">
<div className="flex-shrink-0">
<svg
className="w-5 h-5 text-orange-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"
/>
</svg>
</div>
<div>
<h3
className={`text-base font-medium ${themeMode === 'dark' ? 'text-white' : 'text-gray-900'}`}
>
{t('deleteConfirmationTitle')}
</h3>
</div>
</div>
<div className="flex justify-end gap-2">
<button
type="button"
className={`px-3 py-1.5 text-sm font-medium rounded border transition-colors ${
themeMode === 'dark'
? 'border-gray-600 text-gray-300 hover:bg-gray-600'
: 'border-gray-300 text-gray-700 hover:bg-gray-50'
}`}
onClick={() => setShowDeleteConfirm(false)}
>
{t('deleteConfirmationCancel')}
</button>
<button
type="button"
className="px-3 py-1.5 text-sm font-medium text-white bg-red-600 border border-transparent rounded hover:bg-red-700 transition-colors"
onClick={() => {
handleDeleteSection();
setShowDeleteConfirm(false);
}}
>
{t('deleteConfirmationOk')}
</button>
</div>
</div>
</div>
</div>
</Portal>
)}
<div className="enhanced-kanban-group-tasks"> <div className="enhanced-kanban-group-tasks">
{/* Create card at top */} {/* Create card at top */}
{showNewCardTop && ( {showNewCardTop && (