001package com.studentgui.apppages; 002 003import java.awt.BorderLayout; 004import java.awt.Font; 005import java.awt.GridBagConstraints; 006import java.awt.GridBagLayout; 007import java.awt.Insets; 008import java.awt.event.ActionEvent; 009import java.awt.event.KeyEvent; 010import java.sql.SQLException; 011import java.time.LocalDate; 012 013import javax.swing.JButton; 014import javax.swing.JLabel; 015import javax.swing.JOptionPane; 016import javax.swing.JPanel; 017import javax.swing.JScrollPane; 018import javax.swing.JTextArea; 019import javax.swing.SwingUtilities; 020 021import org.slf4j.Logger; 022import org.slf4j.LoggerFactory; 023 024/** 025 * Observational notes page for documenting unstructured student behaviors and progress. 026 * 027 * <p>Similar to {@link SessionNotes} but intended for ongoing observational records rather than 028 * post-session reflections. Provides a multi-line text area for educators to capture qualitative 029 * observations throughout or across multiple sessions.</p> 030 * 031 * <p><b>Typical Use Cases:</b></p> 032 * <ul> 033 * <li>Recording specific skill demonstrations observed in real-time (e.g., "Student independently located Braille cell for letter 'G' after 2 attempts")</li> 034 * <li>Documenting spontaneous behaviors or breakthroughs (e.g., "First time student used VoiceOver gestures without prompting")</li> 035 * <li>Noting patterns over time (e.g., "Third session this week where student requested breaks during Abacus work")</li> 036 * <li>Functional vision assessments and CVI-related observations</li> 037 * </ul> 038 * 039 * <p><b>Data Persistence:</b></p> 040 * <ul> 041 * <li>Notes saved via {@link com.studentgui.apphelpers.Database#saveSessionNotes} to {@code ProgressSession.notes} column</li> 042 * <li>Associated with an Observations progress type for categorization</li> 043 * <li>Dummy assessment result (code="OBS_NOTE", score=0) inserted to satisfy schema constraints</li> 044 * <li>JSON export: {@code StudentDataFiles/<student>/Sessions/Observations/Observations-<sessionId>-<timestamp>.json}</li> 045 * </ul> 046 * 047 * <p>No plots or quantitative reports are generated. This page does not implement listener interfaces 048 * and operates on static student/date parameters set at construction time.</p> 049 * 050 * @see com.studentgui.apphelpers.Database#saveSessionNotes 051 * @see com.studentgui.apphelpers.dto.NotesPayload 052 * @see SessionNotes 053 */ 054public class Observations extends JPanel { 055 private static final Logger LOG = LoggerFactory.getLogger(Observations.class); 056 /** Multi-line text area for entering observational notes. */ 057 private final JTextArea notesArea; 058 059 /** Selected student's display name (may be null) for this observation session. */ 060 private final String studentNameParam; 061 062 /** Date associated with the recorded observations. */ 063 private final LocalDate dateParam; 064 065 /** 066 * Create an Observations page for the given student and date. 067 * 068 * @param studentName student display name (may be null when no student selected) 069 * @param date the date this observation applies to 070 */ 071 public Observations(String studentName, LocalDate date) { 072 this.studentNameParam = (studentName == null || studentName.trim().isEmpty()) ? com.studentgui.apphelpers.Helpers.defaultStudent() : studentName; 073 this.dateParam = date; 074 setLayout(new BorderLayout()); 075 076 JPanel p = new JPanel(new GridBagLayout()); 077 JPanel view = new JPanel(new BorderLayout()); 078 view.add(p, BorderLayout.NORTH); 079 view.setBorder(javax.swing.BorderFactory.createEmptyBorder(20,20,20,20)); 080 JScrollPane scroll = new JScrollPane(view); 081 scroll.getAccessibleContext().setAccessibleName("Observations data entry scroll pane"); 082 GridBagConstraints gbc = new GridBagConstraints(); gbc.insets=new Insets(2,2,2,2); gbc.fill = GridBagConstraints.BOTH; gbc.anchor = GridBagConstraints.NORTHWEST; 083 JLabel title = new JLabel("Observations", JLabel.LEFT); 084 title.setFont(title.getFont().deriveFont(Font.BOLD,28f)); 085 title.getAccessibleContext().setAccessibleName("Observations Title"); 086 gbc.gridx=0; gbc.gridy=0; gbc.gridwidth=1; p.add(title, gbc); 087 088 gbc.gridy=1; gbc.gridx=0; JLabel notesLabel = new JLabel("Notes:"); p.add(notesLabel, gbc); 089 gbc.gridy=2; gbc.gridx=0; notesArea = new JTextArea(8,40); notesArea.setLineWrap(true); notesArea.setWrapStyleWord(true); notesArea.setToolTipText("Enter observational notes for the student"); notesArea.getAccessibleContext().setAccessibleName("Observations notes"); p.add(notesArea, gbc); 090 notesLabel.setLabelFor(notesArea); 091 092 // Filler so the scroll content has room and the form is visible (prevents 093 // the shared graph in SOUTH from visually dominating the view) 094 gbc.gridy = 3; gbc.gridx = 0; gbc.gridwidth = GridBagConstraints.REMAINDER; gbc.weighty = 1.0; 095 p.add(new JPanel(), gbc); 096 gbc.weighty = 0.0; gbc.gridwidth = 1; 097 098 gbc.gridy = 4; JButton submit = new JButton("Save Notes"); 099 submit.addActionListener((ActionEvent e)-> saveNotes()); 100 submit.setMnemonic(KeyEvent.VK_S); 101 submit.setToolTipText("Save observational notes (Alt+S)"); 102 submit.getAccessibleContext().setAccessibleName("Save Observations Notes"); 103 gbc.gridx = 0; gbc.anchor = GridBagConstraints.WEST; 104 p.add(submit, gbc); 105 // consume remaining columns so layout stays consistent 106 gbc.gridx = 1; gbc.gridwidth = GridBagConstraints.REMAINDER; p.add(new JPanel(), gbc); 107 108 add(scroll, BorderLayout.CENTER); 109 110 SwingUtilities.invokeLater(()->{ p.setPreferredSize(p.getPreferredSize()); revalidate(); }); 111 112 com.studentgui.apphelpers.Helpers.createFolderHierarchy(); 113 } 114 115 /** 116 * Persist the contents of the notes area into the canonical database. 117 * Creates or re-uses the student, progress type and session records as needed. 118 */ 119 private void saveNotes() { 120 if (this.studentNameParam == null || this.studentNameParam.trim().isEmpty()) { 121 JOptionPane.showMessageDialog(this, "Please select a student before saving observations.", "Missing student", JOptionPane.WARNING_MESSAGE); 122 return; 123 } 124 125 try { 126 int studentId = com.studentgui.apphelpers.Database.getOrCreateStudent(this.studentNameParam); 127 int ptId = com.studentgui.apphelpers.Database.getOrCreateProgressType("Observations"); 128 int sessionId = com.studentgui.apphelpers.Database.createProgressSession(studentId, ptId, this.dateParam); 129 String notes = notesArea.getText(); 130 com.studentgui.apphelpers.Database.insertAssessmentResults(sessionId, ptId, new String[]{"OBS_NOTE"}, new int[]{0}); 131 // store the notes in the ProgressSession.notes column via helper 132 com.studentgui.apphelpers.Database.saveSessionNotes(sessionId, notes); 133 LOG.info("Saved observations for {}", studentNameParam); 134 com.studentgui.apphelpers.UiNotifier.show("Observations saved."); 135 com.studentgui.apphelpers.dto.NotesPayload payload = new com.studentgui.apphelpers.dto.NotesPayload(sessionId, notes); 136 java.nio.file.Path jsonOut = com.studentgui.apphelpers.SessionJsonWriter.writeSessionJson(this.studentNameParam, "Observations", payload, sessionId); 137 if (jsonOut == null) { 138 LOG.warn("Unable to save Observations session JSON for sessionId={}", sessionId); 139 } 140 } catch (SQLException ex) { 141 LOG.error("Error saving observations", ex); 142 JOptionPane.showMessageDialog(this, "Database error saving observations: " + ex.getMessage(), "Database error", JOptionPane.ERROR_MESSAGE); 143 } 144 } 145}