From 328fc6f307e0640c82ae343a0be912707179d9c0 Mon Sep 17 00:00:00 2001 From: jason Date: Sat, 7 Mar 2026 09:43:54 -0600 Subject: [PATCH] =?UTF-8?q?feat:=20EmployeeNotes=20component=20=E2=80=94?= =?UTF-8?q?=20inline=20free-text=20notes=20with=20quick-add=20HR=20flag=20?= =?UTF-8?q?tags?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/EmployeeNotes.jsx | 146 ++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 client/src/components/EmployeeNotes.jsx diff --git a/client/src/components/EmployeeNotes.jsx b/client/src/components/EmployeeNotes.jsx new file mode 100644 index 0000000..26cf619 --- /dev/null +++ b/client/src/components/EmployeeNotes.jsx @@ -0,0 +1,146 @@ +import React, { useState } from 'react'; +import axios from 'axios'; + +const s = { + wrapper: { marginTop: '20px' }, + sectionHd: { + fontSize: '13px', fontWeight: 700, color: '#f8f9fa', textTransform: 'uppercase', + letterSpacing: '0.5px', marginBottom: '8px', + }, + display: { + background: '#181924', border: '1px solid #2a2b3a', borderRadius: '6px', + padding: '10px 12px', fontSize: '13px', color: '#f8f9fa', minHeight: '36px', + cursor: 'pointer', position: 'relative', + }, + displayEmpty: { + color: '#555770', fontStyle: 'italic', + }, + editHint: { + position: 'absolute', right: '8px', top: '8px', + fontSize: '10px', color: '#555770', + }, + textarea: { + width: '100%', background: '#0d1117', border: '1px solid #4d6fa8', + borderRadius: '6px', color: '#f8f9fa', fontSize: '13px', + padding: '10px 12px', resize: 'vertical', minHeight: '80px', + boxSizing: 'border-box', fontFamily: 'inherit', outline: 'none', + }, + actions: { display: 'flex', gap: '8px', marginTop: '8px' }, + saveBtn: { + background: '#1a3a6b', border: '1px solid #4d6fa8', color: '#90caf9', + borderRadius: '5px', padding: '5px 14px', fontSize: '12px', + cursor: 'pointer', fontWeight: 600, + }, + cancelBtn: { + background: 'none', border: '1px solid #444', color: '#888', + borderRadius: '5px', padding: '5px 14px', fontSize: '12px', + cursor: 'pointer', + }, + saving: { fontSize: '12px', color: '#9ca0b8', alignSelf: 'center' }, + tagRow: { display: 'flex', flexWrap: 'wrap', gap: '6px', marginBottom: '8px' }, + tag: { + display: 'inline-block', padding: '2px 8px', borderRadius: '10px', + fontSize: '11px', fontWeight: 600, background: '#1a2a3a', + color: '#90caf9', border: '1px solid #2a3a5a', cursor: 'default', + }, +}; + +// Quick-add tags for common HR flags +const QUICK_TAGS = ['On PIP', 'Union member', 'Probationary', 'Pending investigation', 'FMLA', 'ADA']; + +export default function EmployeeNotes({ employeeId, initialNotes, onSaved }) { + const [editing, setEditing] = useState(false); + const [draft, setDraft] = useState(initialNotes || ''); + const [saved, setSaved] = useState(initialNotes || ''); + const [saving, setSaving] = useState(false); + + const handleSave = async () => { + setSaving(true); + try { + await axios.patch(`/api/employees/${employeeId}/notes`, { notes: draft }); + setSaved(draft); + setEditing(false); + if (onSaved) onSaved(draft); + } finally { + setSaving(false); + } + }; + + const handleCancel = () => { + setDraft(saved); + setEditing(false); + }; + + const addTag = (tag) => { + const current = draft.trim(); + // Don't add a tag that's already present + if (current.includes(tag)) return; + setDraft(current ? `${current}\n${tag}` : tag); + }; + + // Parse saved notes into display lines + const lines = saved ? saved.split('\n').filter(Boolean) : []; + + return ( +
+
Notes & Flags
+ + {!editing ? ( +
{ setDraft(saved); setEditing(true); }} + title="Click to edit" + > + ✎ edit + {lines.length === 0 ? ( + No notes — click to add + ) : ( +
+ {lines.map((line, i) => ( + {line} + ))} +
+ )} +
+ ) : ( +
+ {/* Quick-add tag buttons */} +
+ {QUICK_TAGS.map(tag => ( + + ))} +
+ +