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:
@@ -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 */}
|
||||||
|
|
||||||
|
|||||||
@@ -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 && (
|
||||||
|
|||||||
Reference in New Issue
Block a user