Skip to content

Commit 5dc7387

Browse files
feat(tasks): Add visual indicator for unsynced task modifications (#310)
- Implement `unsyncedTaskUuids` state to track locally modified tasks - Add red border to task rows for immediate visual feedback - Display pending unsynced count on Sync button via notification badge - Auto-clear unsynced indicators on successful backend sync - Add unit tests for red border visibility, counter, and state clearing Fixes: #143
1 parent ef3c0a6 commit 5dc7387

5 files changed

Lines changed: 556 additions & 11 deletions

File tree

frontend/src/components/HomeComponents/Tasks/TaskDialog.tsx

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export const TaskDialog = ({
6565
onMarkComplete,
6666
onMarkDeleted,
6767
isOverdue,
68+
isUnsynced,
6869
}: EditTaskDialogProps) => {
6970
const handleDialogOpenChange = (open: boolean) => {
7071
if (open) {
@@ -99,6 +100,7 @@ export const TaskDialog = ({
99100
key={index}
100101
className={`border-b cursor-pointer hover:dark:bg-muted/50 hover:bg-black/15 ${
101102
selectedIndex === index ? 'dark:bg-muted/50 bg-black/15' : ''
103+
} ${isUnsynced ? 'border-l-4 border-l-red-500' : ''}
102104
}`}
103105
data-selected={selectedIndex === index}
104106
onClick={() => {
@@ -222,6 +224,7 @@ export const TaskDialog = ({
222224
<Button
223225
variant="ghost"
224226
size="icon"
227+
aria-label="save"
225228
onClick={() => {
226229
onSaveDescription(
227230
task,
@@ -235,6 +238,7 @@ export const TaskDialog = ({
235238
<Button
236239
variant="ghost"
237240
size="icon"
241+
aria-label="cancel"
238242
onClick={handleCancelClick}
239243
>
240244
<XIcon className="h-4 w-4 text-red-500" />
@@ -247,6 +251,7 @@ export const TaskDialog = ({
247251
<Button
248252
variant="ghost"
249253
size="icon"
254+
aria-label="edit"
250255
onClick={() => handleEditClick(task.description)}
251256
>
252257
<PencilIcon className="h-4 w-4 text-gray-500" />
@@ -294,6 +299,7 @@ export const TaskDialog = ({
294299
<Button
295300
variant="ghost"
296301
size="icon"
302+
aria-label="save"
297303
onClick={() => {
298304
onSaveDueDate(task, editState.editedDueDate);
299305
onUpdateState({ isEditingDueDate: false });
@@ -304,6 +310,7 @@ export const TaskDialog = ({
304310
<Button
305311
variant="ghost"
306312
size="icon"
313+
aria-label="cancel"
307314
onClick={() =>
308315
onUpdateState({
309316
editedDueDate: task.due || '',
@@ -320,6 +327,7 @@ export const TaskDialog = ({
320327
<Button
321328
variant="ghost"
322329
size="icon"
330+
aria-label="edit"
323331
onClick={() => {
324332
onUpdateState({
325333
isEditingDueDate: true,
@@ -379,6 +387,7 @@ export const TaskDialog = ({
379387
<Button
380388
variant="ghost"
381389
size="icon"
390+
aria-label="save"
382391
onClick={() => {
383392
onSaveStartDate(task, editState.editedStartDate);
384393
onUpdateState({ isEditingStartDate: false });
@@ -390,6 +399,7 @@ export const TaskDialog = ({
390399
<Button
391400
variant="ghost"
392401
size="icon"
402+
aria-label="cancel"
393403
onClick={() =>
394404
onUpdateState({
395405
editedStartDate: task.start || '',
@@ -406,6 +416,7 @@ export const TaskDialog = ({
406416
<Button
407417
variant="ghost"
408418
size="icon"
419+
aria-label="edit"
409420
onClick={() => {
410421
onUpdateState({
411422
isEditingStartDate: true,
@@ -462,6 +473,7 @@ export const TaskDialog = ({
462473
<Button
463474
variant="ghost"
464475
size="icon"
476+
aria-label="save"
465477
onClick={() => {
466478
onSaveEndDate(task, editState.editedEndDate);
467479
onUpdateState({ isEditingEndDate: false });
@@ -472,6 +484,7 @@ export const TaskDialog = ({
472484
<Button
473485
variant="ghost"
474486
size="icon"
487+
aria-label="cancel"
475488
onClick={() =>
476489
onUpdateState({
477490
editedEndDate: task.end || '',
@@ -488,6 +501,7 @@ export const TaskDialog = ({
488501
<Button
489502
variant="ghost"
490503
size="icon"
504+
aria-label="edit"
491505
onClick={() => {
492506
onUpdateState({
493507
isEditingEndDate: true,
@@ -544,6 +558,7 @@ export const TaskDialog = ({
544558
<Button
545559
variant="ghost"
546560
size="icon"
561+
aria-label="save"
547562
onClick={() => {
548563
onSaveWaitDate(task, editState.editedWaitDate);
549564
onUpdateState({ isEditingWaitDate: false });
@@ -555,6 +570,7 @@ export const TaskDialog = ({
555570
<Button
556571
variant="ghost"
557572
size="icon"
573+
aria-label="cancel"
558574
onClick={() =>
559575
onUpdateState({
560576
editedWaitDate: task.wait || '',
@@ -571,6 +587,7 @@ export const TaskDialog = ({
571587
<Button
572588
variant="ghost"
573589
size="icon"
590+
aria-label="edit"
574591
onClick={() => {
575592
onUpdateState({
576593
isEditingWaitDate: true,
@@ -618,6 +635,7 @@ export const TaskDialog = ({
618635
<Button
619636
variant="ghost"
620637
size="icon"
638+
aria-label="edit"
621639
onClick={() => {
622640
onUpdateState({
623641
isEditingDepends: true,
@@ -676,7 +694,10 @@ export const TaskDialog = ({
676694
Add Dependency
677695
</Button>
678696
{editState.dependsDropdownOpen && (
679-
<div className="absolute left-0 top-full mt-1 z-50 w-full bg-background border rounded-md shadow-lg max-h-60 overflow-y-auto">
697+
<div
698+
data-testid="dependency-dropdown"
699+
className="absolute left-0 top-full mt-1 z-50 w-full bg-background border rounded-md shadow-lg max-h-60 overflow-y-auto"
700+
>
680701
<Input
681702
type="text"
682703
placeholder="Search tasks..."
@@ -734,6 +755,7 @@ export const TaskDialog = ({
734755
<Button
735756
variant="ghost"
736757
size="icon"
758+
aria-label="save"
737759
onClick={() => {
738760
onSaveDepends(task, editState.editedDepends);
739761
onUpdateState({
@@ -747,6 +769,7 @@ export const TaskDialog = ({
747769
<Button
748770
variant="ghost"
749771
size="icon"
772+
aria-label="cancel"
750773
onClick={() => {
751774
onUpdateState({
752775
isEditingDepends: false,
@@ -788,6 +811,7 @@ export const TaskDialog = ({
788811
<Button
789812
variant="ghost"
790813
size="icon"
814+
aria-label="save"
791815
onClick={() => {
792816
onSavePriority(task, editState.editedPriority);
793817
onUpdateState({ isEditingPriority: false });
@@ -798,6 +822,7 @@ export const TaskDialog = ({
798822
<Button
799823
variant="ghost"
800824
size="icon"
825+
aria-label="cancel"
801826
onClick={() => {
802827
onUpdateState({
803828
editedPriority: task.priority || 'NONE',
@@ -824,6 +849,7 @@ export const TaskDialog = ({
824849
<Button
825850
variant="ghost"
826851
size="icon"
852+
aria-label="edit"
827853
onClick={() =>
828854
onUpdateState({
829855
editedPriority: task.priority || 'NONE',
@@ -915,6 +941,7 @@ export const TaskDialog = ({
915941
<Button
916942
variant="ghost"
917943
size="icon"
944+
aria-label="save"
918945
onClick={() => {
919946
onSaveProject(
920947
task,
@@ -928,6 +955,7 @@ export const TaskDialog = ({
928955
<Button
929956
variant="ghost"
930957
size="icon"
958+
aria-label="cancel"
931959
onClick={() =>
932960
onUpdateState({
933961
editedProject: task.project,
@@ -948,6 +976,7 @@ export const TaskDialog = ({
948976
<Button
949977
variant="ghost"
950978
size="icon"
979+
aria-label="edit"
951980
onClick={() => {
952981
onUpdateState({
953982
editedProject: task.project,
@@ -1010,28 +1039,28 @@ export const TaskDialog = ({
10101039
<Button
10111040
variant="ghost"
10121041
size="icon"
1042+
aria-label="Save tags"
10131043
onClick={() => {
10141044
onSaveTags(task, editState.editedTags);
10151045
onUpdateState({
10161046
isEditingTags: false,
10171047
editTagInput: '',
10181048
});
10191049
}}
1020-
aria-label="Save tags"
10211050
>
10221051
<CheckIcon className="h-4 w-4 text-green-500" />
10231052
</Button>
10241053
<Button
10251054
variant="ghost"
10261055
size="icon"
1056+
aria-label="Cancel editing tags"
10271057
onClick={() => {
10281058
onUpdateState({
10291059
isEditingTags: false,
10301060
editedTags: task.tags || [],
10311061
editTagInput: '',
10321062
});
10331063
}}
1034-
aria-label="Cancel editing tags"
10351064
>
10361065
<XIcon className="h-4 w-4 text-red-500" />
10371066
</Button>
@@ -1084,6 +1113,7 @@ export const TaskDialog = ({
10841113
<Button
10851114
variant="ghost"
10861115
size="icon"
1116+
aria-label="edit"
10871117
onClick={() =>
10881118
onUpdateState({
10891119
isEditingTags: true,
@@ -1140,6 +1170,7 @@ export const TaskDialog = ({
11401170
<Button
11411171
variant="ghost"
11421172
size="icon"
1173+
aria-label="save"
11431174
onClick={() => {
11441175
onSaveEntryDate(task, editState.editedEntryDate);
11451176
onUpdateState({ isEditingEntryDate: false });
@@ -1151,6 +1182,7 @@ export const TaskDialog = ({
11511182
<Button
11521183
variant="ghost"
11531184
size="icon"
1185+
aria-label="cancel"
11541186
onClick={() =>
11551187
onUpdateState({
11561188
isEditingEntryDate: false,
@@ -1171,6 +1203,7 @@ export const TaskDialog = ({
11711203
<Button
11721204
variant="ghost"
11731205
size="icon"
1206+
aria-label="edit"
11741207
onClick={() =>
11751208
onUpdateState({
11761209
isEditingEntryDate: true,
@@ -1240,6 +1273,7 @@ export const TaskDialog = ({
12401273
<Button
12411274
variant="ghost"
12421275
size="icon"
1276+
aria-label="save"
12431277
onClick={() =>
12441278
onSaveRecur(task, editState.editedRecur)
12451279
}
@@ -1249,6 +1283,7 @@ export const TaskDialog = ({
12491283
<Button
12501284
variant="ghost"
12511285
size="icon"
1286+
aria-label="cancel"
12521287
onClick={() =>
12531288
onUpdateState({
12541289
isEditingRecur: false,
@@ -1265,6 +1300,7 @@ export const TaskDialog = ({
12651300
<Button
12661301
variant="ghost"
12671302
size="icon"
1303+
aria-label="edit"
12681304
onClick={() => {
12691305
onUpdateState({
12701306
isEditingRecur: true,
@@ -1448,7 +1484,10 @@ export const TaskDialog = ({
14481484
{task.status == 'pending' ? (
14491485
<Dialog>
14501486
<DialogTrigger asChild className="mr-5">
1451-
<Button id={`mark-task-complete-${task.id}`}>
1487+
<Button
1488+
id={`mark-task-complete-${task.id}`}
1489+
aria-label="complete task"
1490+
>
14521491
Mark As Completed <Key lable="c" />
14531492
</Button>
14541493
</DialogTrigger>
@@ -1488,6 +1527,7 @@ export const TaskDialog = ({
14881527
id={`mark-task-as-deleted-${task.id}`}
14891528
className="mr-4"
14901529
variant={'destructive'}
1530+
aria-label="delete task"
14911531
>
14921532
<Trash2Icon />
14931533
<Key lable="d" />

0 commit comments

Comments
 (0)