• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

jcubic / 10xDevs / 18840571689

27 Oct 2025 12:14PM UTC coverage: 23.648% (-1.9%) from 25.594%
18840571689

push

github

jcubic
update workflow

1783 of 8857 branches covered (20.13%)

Branch coverage included in aggregate %.

761 of 1901 relevant lines covered (40.03%)

1814.76 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

62.5
/src/components/notes/LeftPanel.tsx
1
'use client';
2

3
import { useState, useMemo, memo, useCallback } from 'react';
4
import { Box, Button, Input, Stack, Text } from '@chakra-ui/react';
5
import type { NoteTreeNode, TreeNode } from '@/types/tree';
6

7
import TreeView from '@/components/TreeView';
8
import { ConfirmationDialog } from '@/components/ui/confirmation-dialog';
9

10
interface LeftPanelProps {
11
  notes: NoteTreeNode[];
12
  onNoteSelect: (id: number) => void;
13
  onNewNote: () => void;
14
  onDeleteNote: (id: number) => void;
15
  onRenameNote: (id: number, name: string) => Promise<void>;
16
}
17

18
const LeftPanel = memo(function LeftPanel({
1✔
19
  notes,
20
  onNoteSelect,
21
  onNewNote,
22
  onDeleteNote,
23
  onRenameNote
24
}: LeftPanelProps) {
25
  const [filter, setFilter] = useState('');
6✔
26
  const [deleteDialog, setDeleteDialog] = useState<{
6✔
27
    isOpen: boolean;
28
    note: NoteTreeNode | null;
29
  }>({ isOpen: false, note: null });
30

31
  // Filter and prepare notes for TreeView
32
  const treeData = useMemo<NoteTreeNode[]>(() => {
6✔
33
    const filtered = notes
6✔
34
      .filter((note) => note.name.toLowerCase().includes(filter.toLowerCase()))
12✔
35
      .sort((a, b) => a.id - b.id);
6✔
36
    return filtered;
6✔
37
  }, [notes, filter]);
38

39
  // Handle TreeNode selection
40
  const handleTreeNodeSelect = useCallback(
6✔
41
    (node: TreeNode) => {
42
      onNoteSelect((node as NoteTreeNode).id);
×
43
    },
44
    [onNoteSelect]
45
  );
46

47
  // Handle TreeNode rename
48
  const handleTreeNodeRename = useCallback(
6✔
49
    async (node: TreeNode, newName: string) => {
50
      await onRenameNote?.((node as NoteTreeNode).id, newName);
1✔
51
    },
52
    [onRenameNote]
53
  );
54

55
  // Handle TreeNode delete
56
  const handleTreeNodeDelete = useCallback((node: TreeNode) => {
6✔
57
    setDeleteDialog({ isOpen: true, note: node as NoteTreeNode });
×
58
  }, []);
59

60
  const handleConfirmDelete = useCallback(() => {
6✔
61
    if (deleteDialog.note) {
×
62
      onDeleteNote?.(deleteDialog.note.id);
×
63
    }
64
  }, [deleteDialog.note, onDeleteNote]);
65

66
  const handleCloseDeleteDialog = useCallback(() => {
6✔
67
    setDeleteDialog({ isOpen: false, note: null });
×
68
  }, []);
69

70
  const generateName = useCallback(
6✔
71
    (node: NoteTreeNode) => `${node.data?.dirty ? '* ' : ''}${node.name}`,
16!
72
    []
73
  );
74

75
  const generateTitle = useCallback((node: NoteTreeNode) => `/note/${node.id}`, []);
16✔
76

77
  return (
6✔
78
    <Box as="aside" h="100%" display="flex" flexDirection="column" bg="bg.subtle">
79
      <Stack gap={4} align="stretch" mx={6} mt={6} mb={0}>
80
        <Button colorPalette="blue" variant="solid" onClick={onNewNote}>
81
          New Note
82
        </Button>
83

84
        <Input
85
          p={3}
86
          placeholder="Filter notes..."
87
          value={filter}
88
          onChange={(e) => setFilter(e.target.value)}
×
89
          size="sm"
90
        />
91
      </Stack>
92

93
      <Box
94
        flex={1}
95
        mt={4}
96
        overflow="auto"
97
        w="100%"
98
        borderTop="1px solid"
99
        borderColor="border"
100
        data-testid="note-list"
101
      >
102
        {treeData.length === 0 ? (
6!
103
          <Text textAlign="center" color="fg.muted" fontSize="sm" mt={4}>
104
            {notes.length === 0 ? 'No notes yet' : 'No matching notes'}
×
105
          </Text>
106
        ) : (
107
          <TreeView
108
            data={treeData}
109
            onNodeSelect={handleTreeNodeSelect}
110
            onNodeRename={handleTreeNodeRename}
111
            onNodeDelete={handleTreeNodeDelete}
112
            generateName={generateName}
113
            generateTitle={generateTitle}
114
            title=""
115
          />
116
        )}
117
      </Box>
118

119
      <ConfirmationDialog
120
        isOpen={deleteDialog.isOpen}
121
        onClose={handleCloseDeleteDialog}
122
        onConfirm={handleConfirmDelete}
123
        title="Delete Note"
124
        message={`Are you sure you want to delete "${deleteDialog.note?.name}"? This action cannot be undone.`}
125
        confirmText="Delete"
126
        cancelText="Cancel"
127
        variant="danger"
128
      />
129
    </Box>
130
  );
131
});
132

133
export default LeftPanel;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc