From bbb3645d9934d6901e4e58343d1aa91c8b693cd1 Mon Sep 17 00:00:00 2001 From: Tobias Hopp Date: Wed, 16 Apr 2025 11:17:50 +0200 Subject: [PATCH] Fix birthdate input --- frontend/src/components/FriendshipNetwork.tsx | 507 +++++------------- 1 file changed, 128 insertions(+), 379 deletions(-) diff --git a/frontend/src/components/FriendshipNetwork.tsx b/frontend/src/components/FriendshipNetwork.tsx index f27da40..c435159 100644 --- a/frontend/src/components/FriendshipNetwork.tsx +++ b/frontend/src/components/FriendshipNetwork.tsx @@ -32,17 +32,7 @@ import { // Import custom UI components import { - Button, - Card, - CardBody, - ConfirmDialog, - EmptyState, - FormField, - Modal, - NetworkStats, - Toast, - ToastItem, - Tooltip, + Button, Card, CardBody, ConfirmDialog, EmptyState, FormField, Modal, NetworkStats, Toast, ToastItem, Tooltip, } from './FriendshipNetworkComponents'; // Import visible canvas graph component @@ -58,8 +48,7 @@ interface PersonNode { birthday?: Date | string | null; notes?: string; position?: { - x: number; - y: number; + x: number; y: number; }; } @@ -71,36 +60,6 @@ interface RelationshipEdge { customType?: string; notes?: string; } - -interface CanvasGraphProps { - data: { - nodes: { - id: string; - firstName: string; - lastName: string; - connectionCount: number; - bgColor: string; - x: number; - y: number; - showLabel: boolean; - }[]; - edges: { - id: string; - source: string; - target: string; - color: string; - width: number; - type: RelationshipType; - customType?: string; - }[]; - }; - width: number; - height: number; - zoomLevel: number; - onNodeClick: (nodeId: string) => void; - onNodeDrag: (nodeId: string, x: number, y: number) => void; -} - // Type for form errors interface FormErrors { [key: string]: string; @@ -116,11 +75,7 @@ const RELATIONSHIP_COLORS = { }; const RELATIONSHIP_LABELS = { - freund: 'Friend', - partner: 'Partner', - familie: 'Family', - arbeitskolleg: 'Colleague', - custom: 'Custom', + freund: 'Friend', partner: 'Partner', familie: 'Family', arbeitskolleg: 'Colleague', custom: 'Custom', }; // Main FriendshipNetwork component @@ -143,10 +98,7 @@ const FriendshipNetwork: React.FC = () => { createRelationship, deleteRelationship, refreshNetwork, - updatePersonPosition: updatePersonPositionImpl = ( - id: string, - position: { x: number; y: number }, - ) => { + updatePersonPosition: updatePersonPositionImpl = (id: string, position: { x: number; y: number }) => { console.warn('updatePersonPosition not implemented'); return Promise.resolve(); }, @@ -172,8 +124,7 @@ const FriendshipNetwork: React.FC = () => { const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false); const [helpModalOpen, setHelpModalOpen] = useState(false); const [itemToDelete, setItemToDelete] = useState<{ type: string; id: string }>({ - type: '', - id: '', + type: '', id: '', }); // Form errors @@ -182,21 +133,13 @@ const FriendshipNetwork: React.FC = () => { // Form states const [newPerson, setNewPerson] = useState({ - firstName: '', - lastName: '', - birthday: null as Date | null, - notes: '', + firstName: '', lastName: '', birthday: null as Date | null, notes: '', }); const [editPerson, setEditPerson] = useState(null); const [newRelationship, setNewRelationship] = useState({ - source: '', - target: '', - type: 'freund' as RelationshipType, - customType: '', - notes: '', - bidirectional: true, + source: '', target: '', type: 'freund' as RelationshipType, customType: '', notes: '', bidirectional: true, }); // Filter states @@ -335,9 +278,7 @@ const FriendshipNetwork: React.FC = () => { }, []); // Filtered people and relationships - const filteredPeople = people.filter(person => - `${person.firstName} ${person.lastName}`.toLowerCase().includes(peopleFilter.toLowerCase()), - ); + const filteredPeople = people.filter(person => `${person.firstName} ${person.lastName}`.toLowerCase().includes(peopleFilter.toLowerCase())); const filteredRelationships = relationships.filter(rel => { const source = people.find(p => p._id === rel.source); @@ -345,10 +286,9 @@ const FriendshipNetwork: React.FC = () => { if (!source || !target) return false; - const matchesFilter = - `${source.firstName} ${source.lastName} ${target.firstName} ${target.lastName}` - .toLowerCase() - .includes(relationshipFilter.toLowerCase()); + const matchesFilter = `${source.firstName} ${source.lastName} ${target.firstName} ${target.lastName}` + .toLowerCase() + .includes(relationshipFilter.toLowerCase()); const matchesType = relationshipTypeFilter === 'all' || rel.type === relationshipTypeFilter; @@ -385,21 +325,17 @@ const FriendshipNetwork: React.FC = () => { const theta = index * 2.399; const radius = maxRadius * 0.5 * Math.sqrt(index / (totalNodes + 1)); return { - x: centerX + radius * Math.cos(theta), - y: centerY + radius * Math.sin(theta), + x: centerX + radius * Math.cos(theta), y: centerY + radius * Math.sin(theta), }; } else if (totalNodes <= 11) { const isOuterRing = index >= Math.floor(totalNodes / 2); const ringIndex = isOuterRing ? index - Math.floor(totalNodes / 2) : index; - const ringTotal = isOuterRing - ? totalNodes - Math.floor(totalNodes / 2) + 1 - : Math.floor(totalNodes / 2); + const ringTotal = isOuterRing ? totalNodes - Math.floor(totalNodes / 2) + 1 : Math.floor(totalNodes / 2); const ringRadius = isOuterRing ? maxRadius * 0.8 : maxRadius * 0.4; const angle = (ringIndex / ringTotal) * 2 * Math.PI + (isOuterRing ? 0 : Math.PI / ringTotal); return { - x: centerX + ringRadius * Math.cos(angle), - y: centerY + ringRadius * Math.sin(angle), + x: centerX + ringRadius * Math.cos(angle), y: centerY + ringRadius * Math.sin(angle), }; } else { const clusterCount = Math.max(3, Math.floor(Math.sqrt(totalNodes))); @@ -415,8 +351,7 @@ const FriendshipNetwork: React.FC = () => { const randomDistance = Math.random() * clusterRadius; return { - x: clusterX + randomDistance * Math.cos(randomAngle), - y: clusterY + randomDistance * Math.sin(randomAngle), + x: clusterX + randomDistance * Math.cos(randomAngle), y: clusterY + randomDistance * Math.sin(randomAngle), }; } }, [graphDimensions.width, graphDimensions.height, people.length]); @@ -424,24 +359,16 @@ const FriendshipNetwork: React.FC = () => { // Transform API data to graph format const getGraphData = useCallback(() => { if (!people || !relationships) { - return { nodes: [], edges: [] }; + return { nodes: [], edges: [], links: [] }; } // Create nodes const graphNodes = people.map(person => { - const connectionCount = relationships.filter( - r => r.source === person._id || r.target === person._id, - ).length; + const connectionCount = relationships.filter(r => r.source === person._id || r.target === person._id).length; // Determine if node should be highlighted const isSelected = person._id === selectedPersonId; - const isConnected = selectedPersonId - ? relationships.some( - r => - (r.source === selectedPersonId && r.target === person._id) || - (r.target === selectedPersonId && r.source === person._id), - ) - : false; + const isConnected = selectedPersonId ? relationships.some(r => (r.source === selectedPersonId && r.target === person._id) || (r.target === selectedPersonId && r.source === person._id)) : false; // Determine background color based on connection count or highlight state let bgColor; @@ -479,23 +406,16 @@ const FriendshipNetwork: React.FC = () => { const width = rel.type === 'partner' ? 4 : rel.type === 'familie' ? 3 : 2; // Highlight edges connected to selected node - const isHighlighted = - selectedPersonId && - settings.highlightConnections && - (rel.source === selectedPersonId || rel.target === selectedPersonId); + const isHighlighted = selectedPersonId && settings.highlightConnections && (rel.source === selectedPersonId || rel.target === selectedPersonId); return { - id: rel._id, - source: rel.source, - target: rel.target, - color: isHighlighted ? '#F472B6' : color, // Pink color for highlighted edges + id: rel._id, source: rel.source, target: rel.target, color: isHighlighted ? '#F472B6' : color, // Pink color for highlighted edges width: isHighlighted ? width + 1 : width, // Slightly thicker for highlighted - type: rel.type, - customType: rel.customType, + type: rel.type, customType: rel.customType, }; }); - return { nodes: graphNodes, edges: graphEdges }; + return { nodes: graphNodes, edges: graphEdges, links: [] }; }, [people, relationships, settings.showLabels, settings.highlightConnections, selectedPersonId]); // Validate person form @@ -535,13 +455,7 @@ const FriendshipNetwork: React.FC = () => { // Check if relationship already exists if (relationship.source && relationship.target) { - const existingRelationship = relationships.find( - r => - (r.source === relationship.source && r.target === relationship.target) || - (relationship.bidirectional && - r.source === relationship.target && - r.target === relationship.source), - ); + const existingRelationship = relationships.find(r => (r.source === relationship.source && r.target === relationship.target) || (relationship.bidirectional && r.source === relationship.target && r.target === relationship.source)); if (existingRelationship) { errors.general = 'This relationship already exists'; @@ -573,10 +487,7 @@ const FriendshipNetwork: React.FC = () => { // Reset form and close modal setNewPerson({ - firstName: '', - lastName: '', - birthday: null, - notes: '', + firstName: '', lastName: '', birthday: null, notes: '', }); setPersonModalOpen(false); @@ -619,32 +530,19 @@ const FriendshipNetwork: React.FC = () => { // Create the relationship createRelationship({ - source, - target, - type, - customType: type === 'custom' ? customType : undefined, - notes, + source, target, type, customType: type === 'custom' ? customType : undefined, notes, }); // Create bidirectional relationship if selected if (bidirectional && source !== target) { createRelationship({ - source: target, - target: source, - type, - customType: type === 'custom' ? customType : undefined, - notes, + source: target, target: source, type, customType: type === 'custom' ? customType : undefined, notes, }); } // Reset form and close modal setNewRelationship({ - source: '', - target: '', - type: 'freund', - customType: '', - notes: '', - bidirectional: true, + source: '', target: '', type: 'freund', customType: '', notes: '', bidirectional: true, }); setRelationshipModalOpen(false); @@ -725,21 +623,18 @@ const FriendshipNetwork: React.FC = () => { // Loading state if (loading) { - return ( -
+ return (

Loading your network...

-
- ); +
); } // Error state if (error) { - return ( -
+ return (

Error @@ -754,15 +649,13 @@ const FriendshipNetwork: React.FC = () => { Back to Networks

-
- ); +
); } // Generate graph data const graphData = getGraphData(); - return ( -
+ return (
{/* Sidebar Toggle Button */}
{/* Tab Content */} - {sidebarTab === 'overview' && ( -
+ {sidebarTab === 'overview' && (

About This Network

@@ -907,8 +787,7 @@ const FriendshipNetwork: React.FC = () => { {RELATIONSHIP_LABELS[type as RelationshipType]} -
- ))} +
))}
@@ -931,11 +810,9 @@ const FriendshipNetwork: React.FC = () => { Help - - )} + )} - {sidebarTab === 'people' && ( -
+ {sidebarTab === 'people' && (
{
- {sortedPeople.length > 0 ? ( - sortedPeople.map(person => { - const connectionCount = relationships.filter( - r => r.source === person._id || r.target === person._id, - ).length; + {sortedPeople.length > 0 ? (sortedPeople.map(person => { + const connectionCount = relationships.filter(r => r.source === person._id || r.target === person._id).length; - return ( -
0 - ? 'border-l-indigo-500' - : 'border-l-slate-700' - }`} + cursor-pointer border-l-4 ${selectedPersonId === person._id ? 'border-l-pink-500' : connectionCount > 0 ? 'border-l-indigo-500' : 'border-l-slate-700'}`} onClick={() => { openPersonDetail(person); setSelectedPersonId(person._id); @@ -1013,38 +880,24 @@ const FriendshipNetwork: React.FC = () => {
-
- ); - }) - ) : ( - ); + })) : (} - action={ - !peopleFilter && ( - - ) - } - /> - )} + action={!peopleFilter && ()} + />)}
-
- )} + )} - {sidebarTab === 'relations' && ( -
+ {sidebarTab === 'relations' && (
{
- {Object.entries(RELATIONSHIP_COLORS).map(([type, color]) => ( - - ))} + ))}
- {filteredRelationships.length > 0 ? ( - filteredRelationships.map(rel => { + {filteredRelationships.length > 0 ? (filteredRelationships.map(rel => { const source = people.find(p => p._id === rel.source); const target = people.find(p => p._id === rel.target); if (!source || !target) return null; - return ( -
@@ -1140,9 +977,7 @@ const FriendshipNetwork: React.FC = () => { style={{ backgroundColor: RELATIONSHIP_COLORS[rel.type] }} > - {rel.type === 'custom' - ? rel.customType - : RELATIONSHIP_LABELS[rel.type]} + {rel.type === 'custom' ? rel.customType : RELATIONSHIP_LABELS[rel.type]}
@@ -1157,40 +992,22 @@ const FriendshipNetwork: React.FC = () => {
-
- ); - }) - ) : ( - ); + })) : (} - action={ - !relationshipFilter && - relationshipTypeFilter === 'all' && ( - - ) - } - /> - )} + action={!relationshipFilter && relationshipTypeFilter === 'all' && ()} + />)}
-
- )} +
)} @@ -1200,9 +1017,7 @@ const FriendshipNetwork: React.FC = () => { {graphDimensions.width <= 0 || graphDimensions.height <= 0 ? (
-
- ) : ( - ) : ( { onNodeDrag={(nodeId, x, y) => { updatePersonPosition(nodeId, { x, y }).then(); }} - /> - )} + />)} {/* Empty state overlay */} {people.length === 0 && ( @@ -1234,12 +1048,10 @@ const FriendshipNetwork: React.FC = () => { Add Your First Person - - )} + )} {/* Interaction hint */} - {people.length > 0 && interactionHint && ( -
0 && interactionHint && (
@@ -1251,8 +1063,7 @@ const FriendshipNetwork: React.FC = () => { > -
- )} +
)} {/* Graph controls */}
@@ -1330,8 +1141,7 @@ const FriendshipNetwork: React.FC = () => { {personFormErrors.general && (
{personFormErrors.general} -
- )} +
)} { id="birthday" selected={newPerson.birthday} onChange={date => setNewPerson({ ...newPerson, birthday: date })} - dateFormat="MMMM d, yyyy" + dateFormat="dd.MM.yyyy" placeholderText="Select birthday" className="w-full bg-slate-700 border border-slate-600 rounded-md p-2 focus:outline-none focus:ring-2 focus:ring-indigo-500 text-white" @@ -1416,8 +1226,7 @@ const FriendshipNetwork: React.FC = () => { {relationshipFormErrors.general && (
{relationshipFormErrors.general} -
- )} + )} { onChange={e => setNewRelationship({ ...newRelationship, source: e.target.value })} > - {sortedPeople.map(person => ( - - ))} + ))} @@ -1455,11 +1262,9 @@ const FriendshipNetwork: React.FC = () => { onChange={e => setNewRelationship({ ...newRelationship, target: e.target.value })} > - {sortedPeople.map(person => ( - - ))} + ))}
@@ -1469,23 +1274,17 @@ const FriendshipNetwork: React.FC = () => { className="w-full bg-slate-700 border border-slate-600 rounded-md p-2 focus:outline-none focus:ring-2 focus:ring-indigo-500 text-white" value={newRelationship.type} - onChange={e => - setNewRelationship({ - ...newRelationship, - type: e.target.value as RelationshipType, - }) - } + onChange={e => setNewRelationship({ + ...newRelationship, type: e.target.value as RelationshipType, + })} > - {Object.entries(RELATIONSHIP_LABELS).map(([value, label]) => ( - - ))} + ))} - {newRelationship.type === 'custom' && ( - { rounded-md p-2 focus:outline-none focus:ring-2 focus:ring-indigo-500 text-white`} placeholder="Enter custom relationship type" value={newRelationship.customType} - onChange={e => - setNewRelationship({ - ...newRelationship, - customType: e.target.value, - }) - } + onChange={e => setNewRelationship({ + ...newRelationship, customType: e.target.value, + })} /> - - )} + )}