Skip to content

Commit 67b7795

Browse files
committed
feat: aggiunta funzionalità di modifica e gestione delle categorie nella chat
1 parent 7a4d07a commit 67b7795

5 files changed

Lines changed: 289 additions & 25 deletions

File tree

src/components/BotChat/MessageBubble.tsx

Lines changed: 137 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { useEffect, useRef, useState } from 'react';
2-
import { StyleSheet, View, Text, Animated } from 'react-native';
2+
import { StyleSheet, View, Text, Animated, Alert } from 'react-native';
33
import { MessageBubbleProps, ToolWidget, TaskItem } from './types';
44
import TaskListBubble from './TaskListBubble'; // Nuovo componente card-based
55
import TaskTableBubble from './TaskTableBubble'; // Mantieni per backward compatibility
@@ -8,8 +8,10 @@ import WidgetBubble from './widgets/WidgetBubble';
88
import VisualizationModal from './widgets/VisualizationModal';
99
import ItemDetailModal from './widgets/ItemDetailModal';
1010
import TaskEditModal from '../Task/TaskEditModal';
11+
import EditCategoryModal from '../Category/EditCategoryModal';
12+
import CategoryMenu from '../Category/CategoryMenu';
1113
import { Task as TaskType } from '../../services/taskService';
12-
import { updateTask } from '../../services/taskService';
14+
import { updateTask, updateCategory, deleteCategory } from '../../services/taskService';
1315

1416
const MessageBubble: React.FC<MessageBubbleProps> = ({ message, style }) => {
1517
const isBot = message.sender === 'bot';
@@ -31,6 +33,15 @@ const MessageBubble: React.FC<MessageBubbleProps> = ({ message, style }) => {
3133
const [taskEditModalVisible, setTaskEditModalVisible] = useState(false);
3234
const [selectedTaskForEdit, setSelectedTaskForEdit] = useState<TaskType | null>(null);
3335

36+
// Stato per category edit modal e menu
37+
const [categoryEditModalVisible, setCategoryEditModalVisible] = useState(false);
38+
const [categoryMenuVisible, setCategoryMenuVisible] = useState(false);
39+
const [selectedCategoryForEdit, setSelectedCategoryForEdit] = useState<any>(null);
40+
const [editCategoryName, setEditCategoryName] = useState('');
41+
const [editCategoryDescription, setEditCategoryDescription] = useState('');
42+
const [isEditingCategory, setIsEditingCategory] = useState(false);
43+
const [isDeletingCategory, setIsDeletingCategory] = useState(false);
44+
3445
// Animazioni per i punti di streaming
3546
const streamingDot1 = useRef(new Animated.Value(0.5)).current;
3647
const streamingDot2 = useRef(new Animated.Value(0.5)).current;
@@ -244,6 +255,100 @@ const MessageBubble: React.FC<MessageBubbleProps> = ({ message, style }) => {
244255
}
245256
};
246257

258+
// Handler per aprire il menu della categoria
259+
const handleCategoryPress = (category: any) => {
260+
console.log('[MessageBubble] handleCategoryPress called with:', category);
261+
setSelectedCategoryForEdit(category);
262+
setCategoryMenuVisible(true);
263+
};
264+
265+
// Handler per chiudere il menu della categoria
266+
const handleCloseCategoryMenu = () => {
267+
setCategoryMenuVisible(false);
268+
};
269+
270+
// Handler per aprire la modal di modifica dalla voce menu
271+
const handleEditCategory = () => {
272+
console.log('[MessageBubble] handleEditCategory - selectedCategoryForEdit:', selectedCategoryForEdit);
273+
console.log('[MessageBubble] handleEditCategory - name:', selectedCategoryForEdit?.name);
274+
console.log('[MessageBubble] handleEditCategory - description:', selectedCategoryForEdit?.description);
275+
setCategoryMenuVisible(false);
276+
setEditCategoryName(selectedCategoryForEdit?.name || '');
277+
setEditCategoryDescription(selectedCategoryForEdit?.description || '');
278+
setCategoryEditModalVisible(true);
279+
};
280+
281+
// Handler per salvare le modifiche alla categoria
282+
const handleSaveCategory = async () => {
283+
if (!selectedCategoryForEdit) return;
284+
285+
if (editCategoryName.trim() === '') {
286+
Alert.alert('Errore', 'Il nome della categoria non può essere vuoto');
287+
return;
288+
}
289+
290+
setIsEditingCategory(true);
291+
try {
292+
await updateCategory(selectedCategoryForEdit.name, {
293+
name: editCategoryName.trim(),
294+
description: editCategoryDescription.trim()
295+
});
296+
297+
Alert.alert('Successo', 'Categoria aggiornata con successo');
298+
setCategoryEditModalVisible(false);
299+
setSelectedCategoryForEdit(null);
300+
} catch (error) {
301+
console.error('[MessageBubble] Error updating category:', error);
302+
Alert.alert('Errore', 'Impossibile aggiornare la categoria. Riprova più tardi.');
303+
} finally {
304+
setIsEditingCategory(false);
305+
}
306+
};
307+
308+
// Handler per chiudere la modal di modifica categoria
309+
const handleCancelCategoryEdit = () => {
310+
setCategoryEditModalVisible(false);
311+
setEditCategoryName('');
312+
setEditCategoryDescription('');
313+
};
314+
315+
// Handler per eliminare una categoria
316+
const handleDeleteCategory = async () => {
317+
if (!selectedCategoryForEdit) return;
318+
319+
Alert.alert(
320+
'Conferma eliminazione',
321+
`Sei sicuro di voler eliminare la categoria "${selectedCategoryForEdit.name}"?`,
322+
[
323+
{ text: 'Annulla', style: 'cancel' },
324+
{
325+
text: 'Elimina',
326+
style: 'destructive',
327+
onPress: async () => {
328+
setIsDeletingCategory(true);
329+
try {
330+
await deleteCategory(selectedCategoryForEdit.name);
331+
Alert.alert('Successo', 'Categoria eliminata con successo');
332+
setCategoryMenuVisible(false);
333+
setSelectedCategoryForEdit(null);
334+
} catch (error) {
335+
console.error('[MessageBubble] Error deleting category:', error);
336+
Alert.alert('Errore', 'Impossibile eliminare la categoria. Riprova più tardi.');
337+
} finally {
338+
setIsDeletingCategory(false);
339+
}
340+
},
341+
},
342+
]
343+
);
344+
};
345+
346+
// Handler placeholder per condivisione categoria
347+
const handleShareCategory = () => {
348+
setCategoryMenuVisible(false);
349+
Alert.alert('Info', 'Funzionalità di condivisione non ancora disponibile nella chat');
350+
};
351+
247352
return (
248353
<Animated.View style={[
249354
styles.messageContainer,
@@ -264,6 +369,7 @@ const MessageBubble: React.FC<MessageBubbleProps> = ({ message, style }) => {
264369
onOpenVisualization={handleOpenVisualization}
265370
onOpenItemDetail={handleOpenItemDetail}
266371
onTaskPress={handleTaskPress}
372+
onCategoryPress={handleCategoryPress}
267373
/>
268374
))}
269375
</View>
@@ -303,6 +409,7 @@ const MessageBubble: React.FC<MessageBubbleProps> = ({ message, style }) => {
303409
widget={selectedVisualizationWidget}
304410
onClose={() => setVisualizationModalVisible(false)}
305411
onItemPress={handleOpenItemDetail}
412+
onCategoryPress={handleCategoryPress}
306413
/>
307414
)}
308415

@@ -326,6 +433,34 @@ const MessageBubble: React.FC<MessageBubbleProps> = ({ message, style }) => {
326433
onSave={handleSaveTask}
327434
/>
328435
)}
436+
437+
{/* Category Menu */}
438+
{selectedCategoryForEdit && (
439+
<CategoryMenu
440+
visible={categoryMenuVisible}
441+
onClose={handleCloseCategoryMenu}
442+
onEdit={handleEditCategory}
443+
onDelete={handleDeleteCategory}
444+
onShare={handleShareCategory}
445+
isDeleting={isDeletingCategory}
446+
isOwned={selectedCategoryForEdit.isOwned !== undefined ? selectedCategoryForEdit.isOwned : true}
447+
permissionLevel={selectedCategoryForEdit.permissionLevel || "READ_WRITE"}
448+
/>
449+
)}
450+
451+
{/* Category Edit Modal */}
452+
{selectedCategoryForEdit && (
453+
<EditCategoryModal
454+
visible={categoryEditModalVisible}
455+
onClose={handleCancelCategoryEdit}
456+
onSave={handleSaveCategory}
457+
title={editCategoryName}
458+
description={editCategoryDescription}
459+
onTitleChange={setEditCategoryName}
460+
onDescriptionChange={setEditCategoryDescription}
461+
isEditing={isEditingCategory}
462+
/>
463+
)}
329464
</Animated.View>
330465
);
331466
};

src/components/BotChat/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ export interface WidgetBubbleProps {
201201
onOpenVisualization?: (widget: ToolWidget) => void;
202202
onOpenItemDetail?: (item: any, type: 'task' | 'category' | 'note') => void;
203203
onTaskPress?: (task: any) => void;
204+
onCategoryPress?: (category: any) => void;
204205
}
205206

206207
// Props per CreationWidgetCard
@@ -215,6 +216,7 @@ export interface VisualizationModalProps {
215216
widget: ToolWidget;
216217
onClose: () => void;
217218
onItemPress?: (item: any, type: 'task' | 'category' | 'note') => void;
219+
onCategoryPress?: (category: any) => void;
218220
}
219221

220222
// Props per ItemDetailModal

src/components/BotChat/widgets/VisualizationModal.tsx

Lines changed: 128 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ import {
88
ScrollView,
99
SafeAreaView,
1010
TextInput,
11+
Image,
1112
} from 'react-native';
1213
import { Ionicons } from '@expo/vector-icons';
1314
import { VisualizationModalProps, TaskListItem } from '../types';
14-
import Category from '../../Category/Category';
1515
import Task from '../../Task/Task';
1616
import { Task as TaskType } from '../../../services/taskService';
1717
import CalendarGrid from '../../Calendar/CalendarGrid';
@@ -26,6 +26,7 @@ const VisualizationModal: React.FC<VisualizationModalProps> = ({
2626
widget,
2727
onClose,
2828
onItemPress,
29+
onCategoryPress,
2930
}) => {
3031
const [selectedDate, setSelectedDate] = useState<string | null>(null);
3132
const [searchQuery, setSearchQuery] = useState('');
@@ -248,19 +249,53 @@ const VisualizationModal: React.FC<VisualizationModalProps> = ({
248249
);
249250

250251
return (
251-
<View key={index} style={styles.categoryCardWrapper}>
252-
<Category
253-
title={item.name}
254-
description={item.description}
255-
imageUrl={isValidUrl ? validImageUrl : undefined}
256-
taskCount={item.taskCount || item.task_count || 0}
257-
categoryId={item.id}
258-
isShared={item.isShared || false}
259-
isOwned={item.isOwned !== undefined ? item.isOwned : true}
260-
ownerName={item.ownerName}
261-
permissionLevel={item.permissionLevel || "READ_WRITE"}
262-
/>
263-
</View>
252+
<TouchableOpacity
253+
key={index}
254+
style={styles.categoryCard}
255+
activeOpacity={0.7}
256+
onPress={() => {
257+
console.log('[VisualizationModal] Category pressed:', item.name);
258+
console.log('[VisualizationModal] onCategoryPress available:', !!onCategoryPress);
259+
if (onCategoryPress) {
260+
onCategoryPress(item);
261+
} else {
262+
console.warn('[VisualizationModal] onCategoryPress is not defined!');
263+
}
264+
}}
265+
>
266+
<View style={styles.categoryIconContainer}>
267+
{isValidUrl ? (
268+
<Image source={{ uri: validImageUrl }} style={styles.categoryImage} />
269+
) : (
270+
<Ionicons name="folder" size={32} color="#007AFF" />
271+
)}
272+
</View>
273+
<View style={styles.categoryTextContainer}>
274+
<Text style={styles.categoryTitle} numberOfLines={1}>
275+
{item.name}
276+
</Text>
277+
{item.description && (
278+
<Text style={styles.categoryDescription} numberOfLines={2}>
279+
{item.description}
280+
</Text>
281+
)}
282+
<View style={styles.categoryMetaContainer}>
283+
<View style={styles.categoryTaskCount}>
284+
<Ionicons name="list-outline" size={14} color="#666666" />
285+
<Text style={styles.categoryTaskCountText}>
286+
{item.taskCount || item.task_count || 0} task
287+
</Text>
288+
</View>
289+
{item.isShared && (
290+
<View style={styles.categorySharedBadge}>
291+
<Ionicons name="people-outline" size={14} color="#007AFF" />
292+
<Text style={styles.categorySharedText}>Condiviso</Text>
293+
</View>
294+
)}
295+
</View>
296+
</View>
297+
<Ionicons name="chevron-forward" size={20} color="#C7C7CC" />
298+
</TouchableOpacity>
264299
);
265300
}
266301

@@ -780,6 +815,85 @@ const styles = StyleSheet.create({
780815
color: '#FFFFFF',
781816
fontFamily: 'System',
782817
},
818+
// Stili per le category card personalizzate nella chat
819+
categoryCard: {
820+
flexDirection: 'row',
821+
alignItems: 'center',
822+
backgroundColor: '#FFFFFF',
823+
borderRadius: 12,
824+
paddingHorizontal: 16,
825+
paddingVertical: 14,
826+
marginBottom: 12,
827+
marginHorizontal: 16,
828+
shadowColor: '#000',
829+
shadowOffset: { width: 0, height: 1 },
830+
shadowOpacity: 0.05,
831+
shadowRadius: 3,
832+
elevation: 1,
833+
borderWidth: 1.5,
834+
borderColor: '#E1E5E9',
835+
},
836+
categoryIconContainer: {
837+
width: 48,
838+
height: 48,
839+
borderRadius: 24,
840+
backgroundColor: '#E5F1FF',
841+
alignItems: 'center',
842+
justifyContent: 'center',
843+
marginRight: 12,
844+
overflow: 'hidden',
845+
},
846+
categoryImage: {
847+
width: 48,
848+
height: 48,
849+
borderRadius: 24,
850+
},
851+
categoryTextContainer: {
852+
flex: 1,
853+
},
854+
categoryTitle: {
855+
fontSize: 17,
856+
fontWeight: '600',
857+
color: '#000000',
858+
marginBottom: 4,
859+
fontFamily: 'System',
860+
},
861+
categoryDescription: {
862+
fontSize: 14,
863+
color: '#666666',
864+
marginBottom: 6,
865+
fontFamily: 'System',
866+
},
867+
categoryMetaContainer: {
868+
flexDirection: 'row',
869+
alignItems: 'center',
870+
gap: 12,
871+
},
872+
categoryTaskCount: {
873+
flexDirection: 'row',
874+
alignItems: 'center',
875+
gap: 4,
876+
},
877+
categoryTaskCountText: {
878+
fontSize: 13,
879+
color: '#666666',
880+
fontFamily: 'System',
881+
},
882+
categorySharedBadge: {
883+
flexDirection: 'row',
884+
alignItems: 'center',
885+
gap: 4,
886+
backgroundColor: '#E5F1FF',
887+
paddingHorizontal: 8,
888+
paddingVertical: 2,
889+
borderRadius: 8,
890+
},
891+
categorySharedText: {
892+
fontSize: 12,
893+
color: '#007AFF',
894+
fontWeight: '600',
895+
fontFamily: 'System',
896+
},
783897
});
784898

785899
export default VisualizationModal;

src/components/BotChat/widgets/VisualizationWidget.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,15 @@ interface VisualizationWidgetProps {
99
widget: ToolWidget;
1010
onOpen: (widget: ToolWidget) => void;
1111
onTaskPress?: (task: any) => void;
12+
onCategoryPress?: (category: any) => void;
1213
}
1314

1415
/**
1516
* Widget per tool di visualizzazione (show_tasks_to_user, show_categories_to_user, show_notes_to_user)
1617
* Per task_list: mostra preview card-based (max 3)
1718
* Per altri tipi: mostra card semplice con pulsante
1819
*/
19-
const VisualizationWidget: React.FC<VisualizationWidgetProps> = ({ widget, onOpen, onTaskPress }) => {
20+
const VisualizationWidget: React.FC<VisualizationWidgetProps> = ({ widget, onOpen, onTaskPress, onCategoryPress }) => {
2021
const output = widget.toolOutput;
2122

2223
// Se è in stato loading e non ha ancora output, mostra lo skeleton loader

0 commit comments

Comments
 (0)