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}