Skip to content

Commit 862dd2c

Browse files
committed
feat: Implemented Add Actions
1 parent dbc0496 commit 862dd2c

File tree

6 files changed

+199
-38
lines changed

6 files changed

+199
-38
lines changed

src/api/statementsApi.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,22 @@ export async function postNewStatement(statement: Statement): Promise<void> {
3030
console.error('Error posting new statement:', error);
3131
}
3232
}
33+
34+
export async function updateStatement(statement: Statement): Promise<void> {
35+
try {
36+
const response = await fetch(`${API_URL}/updateEntry`, {
37+
method: 'PUT', // or PATCH, depending on the backend
38+
headers: {
39+
'Content-Type': 'application/json',
40+
},
41+
body: JSON.stringify(statement),
42+
});
43+
if (!response.ok) {
44+
throw new Error(`HTTP error on update! status: ${response.status}`);
45+
}
46+
const data = await response.json();
47+
console.log('Successfully updated statement:', data);
48+
} catch (error) {
49+
console.error('Error updating statement:', error);
50+
}
51+
}

src/components/statements/ActionPreview.tsx

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from 'react';
22
import { format } from 'date-fns';
33
import { MoreVertical, Edit2, Trash2, Plus } from 'lucide-react';
44
import { Button } from '../ui/button';
5+
import { Input } from '../ui/input';
56
import {
67
DropdownMenu,
78
DropdownMenuTrigger,
@@ -14,7 +15,7 @@ export interface ActionPreviewProps {
1415
actions: Action[];
1516
onEditAction: (actionId: string) => void;
1617
onDeleteAction: (actionId: string) => void;
17-
onAddAction: () => void;
18+
onAddAction: (newAction: { text: string; dueDate: string }) => void;
1819
}
1920

2021
const ActionPreview: React.FC<ActionPreviewProps> = ({
@@ -23,6 +24,21 @@ const ActionPreview: React.FC<ActionPreviewProps> = ({
2324
onDeleteAction,
2425
onAddAction,
2526
}) => {
27+
const [newAction, setNewAction] = React.useState<{
28+
text: string;
29+
dueDate: string;
30+
}>({
31+
text: '',
32+
dueDate: '',
33+
});
34+
35+
const handleAddAction = () => {
36+
if (newAction.text && newAction.dueDate) {
37+
onAddAction(newAction);
38+
setNewAction({ text: '', dueDate: '' });
39+
}
40+
};
41+
2642
return (
2743
<div className='space-y-2 mt-4'>
2844
{actions.map((action) => (
@@ -58,12 +74,23 @@ const ActionPreview: React.FC<ActionPreviewProps> = ({
5874
</DropdownMenu>
5975
</div>
6076
))}
61-
<div
62-
className='bg-gray-50 p-3 rounded-lg flex items-center justify-center cursor-pointer hover:bg-gray-100'
63-
onClick={onAddAction}
64-
>
65-
<Plus className='w-4 h-4 mr-2' />
66-
<span>Add Action</span>
77+
<div className='space-y-2 pt-4 border-t'>
78+
<Input
79+
placeholder='New action'
80+
value={newAction.text}
81+
onChange={(e) => setNewAction({ ...newAction, text: e.target.value })}
82+
/>
83+
<Input
84+
type='date'
85+
value={newAction.dueDate}
86+
onChange={(e) =>
87+
setNewAction({ ...newAction, dueDate: e.target.value })
88+
}
89+
/>
90+
<Button onClick={handleAddAction} className='w-full'>
91+
<Plus className='w-4 h-4 mr-2' />
92+
Add Action
93+
</Button>
6794
</div>
6895
</div>
6996
);
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import React from 'react';
2+
3+
/**
4+
* Props:
5+
* - count: The total number of actions.
6+
* - expanded?: whether the actions are expanded (to style differently).
7+
*
8+
* Behavior:
9+
* - Displays a small pill with "no actions" if count is 0,
10+
* or "1 action" if count is 1, or "X actions" otherwise.
11+
*/
12+
interface ActionsCounterProps {
13+
count: number;
14+
expanded?: boolean;
15+
}
16+
17+
const ActionsCounter: React.FC<ActionsCounterProps> = ({
18+
count,
19+
expanded = false,
20+
}) => {
21+
const baseClasses =
22+
'inline-flex items-center rounded-full px-3 py-1 text-sm transition-colors cursor-pointer';
23+
const normalClasses = 'bg-gray-100 text-gray-600';
24+
const expandedClasses = 'bg-blue-100 text-blue-600';
25+
26+
const displayText =
27+
count === 0
28+
? 'no actions'
29+
: `${count} ${count === 1 ? 'action' : 'actions'}`;
30+
31+
return (
32+
<span
33+
className={`${baseClasses} ${expanded ? expandedClasses : normalClasses}`}
34+
>
35+
{displayText}
36+
</span>
37+
);
38+
};
39+
40+
export default ActionsCounter;

src/components/statements/StatementItem.tsx

Lines changed: 76 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import {
1111
DropdownMenuContent,
1212
DropdownMenuItem,
1313
} from '../ui/dropdown-menu';
14+
import ActionsCounter from './ActionsCounter';
15+
import ActionPreview from './ActionPreview';
1416

1517
export interface StatementItemProps {
1618
statement: Statement;
@@ -29,6 +31,14 @@ export interface StatementItemProps {
2931
onDelete: (statementId: string) => void;
3032
onTogglePublic: (statementId: string) => void;
3133
onEditClick: (statementId: string) => void;
34+
// Optional callbacks for action preview functionality
35+
onEditAction?: (actionId: string) => void;
36+
onDeleteAction?: (actionId: string) => void;
37+
// Optional callback: receives the statement id and new action details.
38+
onAddAction?: (
39+
statementId: string,
40+
newAction: { text: string; dueDate: string }
41+
) => void;
3242
}
3343

3444
const StatementItem: React.FC<StatementItemProps> = ({
@@ -41,7 +51,11 @@ const StatementItem: React.FC<StatementItemProps> = ({
4151
onDelete,
4252
onTogglePublic,
4353
onEditClick,
54+
onEditAction = () => {},
55+
onDeleteAction = () => {},
56+
onAddAction = () => {},
4457
}) => {
58+
const [isActionsExpanded, setIsActionsExpanded] = React.useState(false);
4559
const objectInputRef = useRef<HTMLInputElement>(null);
4660

4761
useEffect(() => {
@@ -145,37 +159,69 @@ const StatementItem: React.FC<StatementItemProps> = ({
145159

146160
// Static view when not in editing mode with grouped Edit and Delete
147161
return (
148-
<div className='flex justify-between items-center bg-gray-100 p-2 rounded'>
149-
<span
150-
className={`inline-flex items-center justify-center px-3 py-2 ${
151-
statement.isPublic ? 'text-green-500' : 'text-gray-400'
152-
}`}
153-
>
154-
{statement.isPublic ? <Eye size={16} /> : <EyeOff size={16} />}
155-
</span>
156-
<span className='flex-1'>{`${statement.subject} ${statement.verb} ${statement.object}`}</span>
157-
<div className='flex items-center space-x-2 ml-auto'>
158-
<DropdownMenu>
159-
<DropdownMenuTrigger asChild>
160-
<button onClick={(e) => e.stopPropagation()}>
161-
<MoreVertical size={18} className='text-gray-500' />
162-
</button>
163-
</DropdownMenuTrigger>
164-
<DropdownMenuContent align='end'>
165-
<DropdownMenuItem onClick={() => onEditClick(statement.id)}>
166-
<Edit2 className='mr-2 h-4 w-4' />
167-
Edit
168-
</DropdownMenuItem>
169-
<DropdownMenuItem
170-
onClick={() => onDelete(statement.id)}
171-
className='text-red-600'
172-
>
173-
<Trash2 className='mr-2 h-4 w-4' />
174-
Delete
175-
</DropdownMenuItem>
176-
</DropdownMenuContent>
177-
</DropdownMenu>
162+
<div className='bg-white border rounded-md p-3 space-y-2 shadow-sm'>
163+
{/* Top row: statement, actions counter, etc. */}
164+
<div className='flex items-center justify-between'>
165+
{/* Left side: privacy icon + statement text */}
166+
<div className='flex items-center space-x-2'>
167+
<span
168+
className={`inline-flex items-center justify-center ${
169+
statement.isPublic ? 'text-green-500' : 'text-gray-400'
170+
}`}
171+
>
172+
{statement.isPublic ? <Eye size={16} /> : <EyeOff size={16} />}
173+
</span>
174+
<span>{`${statement.subject} ${statement.verb} ${statement.object}`}</span>
175+
</div>
176+
177+
{/* Right side: actions counter + dropdown */}
178+
<div className='flex items-center space-x-4'>
179+
{/* Actions counter - click to expand/collapse */}
180+
<div
181+
onClick={() => setIsActionsExpanded((prev) => !prev)}
182+
className='cursor-pointer'
183+
>
184+
<ActionsCounter
185+
count={statement.actions?.length ?? 0}
186+
expanded={isActionsExpanded}
187+
/>
188+
</div>
189+
<DropdownMenu>
190+
<DropdownMenuTrigger asChild>
191+
<button onClick={(e) => e.stopPropagation()}>
192+
<MoreVertical size={18} className='text-gray-500' />
193+
</button>
194+
</DropdownMenuTrigger>
195+
<DropdownMenuContent align='end'>
196+
<DropdownMenuItem onClick={() => onEditClick(statement.id)}>
197+
<Edit2 className='mr-2 h-4 w-4' />
198+
Edit
199+
</DropdownMenuItem>
200+
<DropdownMenuItem
201+
onClick={() => onDelete(statement.id)}
202+
className='text-red-600'
203+
>
204+
<Trash2 className='mr-2 h-4 w-4' />
205+
Delete
206+
</DropdownMenuItem>
207+
</DropdownMenuContent>
208+
</DropdownMenu>
209+
</div>
178210
</div>
211+
212+
{/* Inline actions preview if expanded */}
213+
{isActionsExpanded && (
214+
<div className='mt-2'>
215+
<ActionPreview
216+
actions={statement.actions ?? []}
217+
onEditAction={onEditAction}
218+
onDeleteAction={onDeleteAction}
219+
onAddAction={(newAction) =>
220+
onAddAction && onAddAction(statement.id, newAction)
221+
}
222+
/>
223+
</div>
224+
)}
179225
</div>
180226
);
181227
};

src/components/statements/StatementList.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { ConfirmationDialog } from '../ui/confirmation-dialog';
77
import type { Statement } from '../../../types/types';
88
import preStatements from '../../../data/preStatements.json';
99
import StatementItem from './StatementItem';
10+
import { updateStatement } from '../../api/statementsApi';
1011

1112
const StatementList: React.FC<{ username: string }> = ({ username }) => {
1213
const { state, dispatch } = useStatements();
@@ -128,6 +129,34 @@ const StatementList: React.FC<{ username: string }> = ({ username }) => {
128129
}
129130
};
130131

132+
// NEW: handleAddAction callback for adding a new action to a statement.
133+
const handleAddAction = (
134+
statementId: string,
135+
newAction: { text: string; dueDate: string }
136+
) => {
137+
const actionObj = {
138+
id: Date.now().toString(),
139+
creationDate: new Date().toISOString(),
140+
text: newAction.text,
141+
dueDate: newAction.dueDate,
142+
};
143+
144+
const statementToUpdate = statements.find((s) => s.id === statementId);
145+
if (!statementToUpdate) return;
146+
147+
const updatedActions = statementToUpdate.actions
148+
? [...statementToUpdate.actions, actionObj]
149+
: [actionObj];
150+
151+
const updatedStatement: Statement = {
152+
...statementToUpdate,
153+
actions: updatedActions,
154+
};
155+
156+
dispatch({ type: 'UPDATE_STATEMENT', payload: updatedStatement });
157+
updateStatement(updatedStatement);
158+
};
159+
131160
return (
132161
<div className='mt-8 bg-white rounded-xl shadow-lg p-6 w-full'>
133162
<h2 className='text-xl font-semibold mb-4'>Created Statements</h2>
@@ -146,6 +175,7 @@ const StatementList: React.FC<{ username: string }> = ({ username }) => {
146175
onDelete={handleDeleteClick}
147176
onTogglePublic={handleTogglePublic}
148177
onEditClick={handleEditClick}
178+
onAddAction={handleAddAction}
149179
/>
150180
</li>
151181
))}

src/components/ui/command.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import type { DialogProps } from '@radix-ui/react-dialog';
55
import { DialogTitle } from '@radix-ui/react-dialog';
66
import { Command as CommandPrimitive } from 'cmdk';
77
import { Search } from 'lucide-react';
8-
98
import { cn } from '../../../lib/utils';
109
import { Dialog, DialogContent, DialogDescription } from './dialog';
1110

0 commit comments

Comments
 (0)