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.JComboBox;
015import javax.swing.JLabel;
016import javax.swing.JOptionPane;
017import javax.swing.JPanel;
018import javax.swing.JScrollPane;
019import javax.swing.JTextArea;
020import javax.swing.JTextField;
021import javax.swing.SwingUtilities;
022
023import org.slf4j.Logger;
024import org.slf4j.LoggerFactory;
025
026/**
027 * Structured parent/guardian contact log with validation and freeform notes.
028 *
029 * <p>Provides a comprehensive contact tracking form with structured fields for documenting
030 * communications with parents, guardians, and family members. Unlike the freeform notes pages
031 * ({@link SessionNotes}, {@link Observations}), this page captures both structured metadata
032 * and narrative details to support later reporting and documentation requirements.</p>
033 *
034 * <p><b>Structured Fields:</b></p>
035 * <ul>
036 *   <li><b>Guardian Name:</b> Full name of the parent/guardian contacted</li>
037 *   <li><b>Contact Method:</b> Dropdown selection (Phone, Email, In Person, Other)</li>
038 *   <li><b>Phone Number:</b> Contact phone number (validated format: 7-20 chars, digits/+/()-/space)</li>
039 *   <li><b>Email Address:</b> Contact email (validated format: basic email regex pattern)</li>
040 *   <li><b>Contact Response:</b> Brief summary of the guardian's response or concerns</li>
041 *   <li><b>Contact General:</b> High-level topic or category of the contact (e.g., "Progress Update", "IEP Discussion")</li>
042 *   <li><b>Contact Specific:</b> Specific items discussed or action points (e.g., "Discussed Braille materials order")</li>
043 *   <li><b>Notes:</b> Multi-line freeform notes area for detailed narrative</li>
044 * </ul>
045 *
046 * <p><b>Validation and Error Handling:</b></p>
047 * <ul>
048 *   <li>Email validation: Triggers warning if Contact Method is "Email" and email field doesn't match {@code ^[^@\s]+@[^@\s]+\.[^@\s]+$}</li>
049 *   <li>Phone validation: Triggers warning if Contact Method is "Phone" and phone doesn't match {@code ^[0-9+()\-\s]{7,20}$}</li>
050 *   <li>Validation failures display warning dialogs and do not persist data until corrected</li>
051 * </ul>
052 *
053 * <p><b>Data Persistence:</b></p>
054 * <ul>
055 *   <li>Structured fields persisted via {@link com.studentgui.apphelpers.Database#saveContactLog} to {@code ContactLog} table</li>
056 *   <li>Notes also saved to {@code ProgressSession.notes} column via {@link com.studentgui.apphelpers.Database#saveSessionNotes}</li>
057 *   <li>JSON export: {@code StudentDataFiles/<student>/Sessions/ContactLog/ContactLog-<sessionId>-<timestamp>.json}</li>
058 *   <li>Load Last Contact button retrieves most recent contact record via {@link com.studentgui.apphelpers.Database#fetchLatestContactLog}</li>
059 * </ul>
060 *
061 * <p>No plots are generated (contact logs are non-quantitative). The shared {@link JLineGraph} component
062 * is absent from this page's layout. This page does not implement listener interfaces and operates
063 * on static student/date parameters.</p>
064 *
065 * @see com.studentgui.apphelpers.Database#saveContactLog
066 * @see com.studentgui.apphelpers.Database#fetchLatestContactLog
067 * @see com.studentgui.apphelpers.dto.ContactPayload
068 */
069public class ContactLog extends JPanel {
070    private static final Logger LOG = LoggerFactory.getLogger(ContactLog.class);
071    /** Text area where the user enters contact notes for the selected student. */
072    private final JTextArea notesArea;
073    // additional contact fields
074    /** Guardian or parent name associated with the student. */
075    private final JTextField guardianField;
076    /** Phone number used for contact. */
077    private final JTextField phoneField;
078    /** Email address used for contact. */
079    private final JTextField emailField;
080    /** Method of contact (Phone/Email/In Person/Other). */
081    private final JComboBox<String> contactMethodCombo;
082    /** Short description of the response received during contact. */
083    private final JTextField contactResponseField;
084    /** High-level/general contact notes (summary). */
085    private final JTextField contactGeneralField;
086    /** Specific items or action points discussed during contact. */
087    private final JTextField contactSpecificField;
088
089    /** Selected student display name associated with this page instance (may be null). */
090    private final String studentNameParam;
091
092    /** Session date to associate with saved notes from this page. */
093    private final LocalDate dateParam;
094
095    /**
096     * Construct a ContactLog page for the provided student and date.
097     *
098     * @param studentName selected student display name (may be null)
099     * @param date session date to associate with saved notes
100     * @param graph shared graph component shown under the editor
101     */
102    public ContactLog(String studentName, LocalDate date, JLineGraph graph) {
103    this.studentNameParam = (studentName == null || studentName.trim().isEmpty()) ? com.studentgui.apphelpers.Helpers.defaultStudent() : studentName;
104        this.dateParam = date;
105        setLayout(new BorderLayout());
106
107    JPanel p = new JPanel(new GridBagLayout());
108    JPanel view = new JPanel(new BorderLayout());
109    view.add(p, BorderLayout.NORTH);
110    view.setBorder(javax.swing.BorderFactory.createEmptyBorder(20,20,20,20));
111    JScrollPane scroll = new JScrollPane(view);
112    scroll.getAccessibleContext().setAccessibleName("Contact Log data entry scroll pane");
113    GridBagConstraints gbc = new GridBagConstraints(); gbc.insets=new Insets(2,2,2,2); gbc.fill = GridBagConstraints.HORIZONTAL; gbc.anchor = GridBagConstraints.NORTHWEST;
114    JLabel title = new JLabel("Contact Log"); title.setFont(title.getFont().deriveFont(Font.BOLD,28f)); title.setHorizontalAlignment(JLabel.LEFT); gbc.gridx=0; gbc.gridy=0; gbc.gridwidth=2; p.add(title, gbc);
115
116    // Structured contact fields (placed above notes)
117    int row = 1;
118    gbc.gridwidth = 1;
119    int globalLabel = com.studentgui.uicomp.PhaseScoreField.getGlobalLabelWidth();
120    gbc.gridx = 0; gbc.gridy = row; JLabel guardianLabel = new JLabel("Guardian Name:"); guardianLabel.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 24)); guardianLabel.setPreferredSize(new java.awt.Dimension(globalLabel, guardianLabel.getPreferredSize().height)); p.add(guardianLabel, gbc);
121    guardianField = new JTextField(24); guardianField.setName("contactlog_guardian"); gbc.gridx = 1; p.add(guardianField, gbc);
122    row++;
123    gbc.gridx = 0; gbc.gridy = row; JLabel methodLabel = new JLabel("Contact Method:"); methodLabel.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 24)); methodLabel.setPreferredSize(new java.awt.Dimension(globalLabel, methodLabel.getPreferredSize().height)); p.add(methodLabel, gbc);
124    contactMethodCombo = new JComboBox<>(new String[]{"Phone","Email","In Person","Other"}); contactMethodCombo.setName("contactlog_method"); gbc.gridx = 1; p.add(contactMethodCombo, gbc);
125    row++;
126    gbc.gridx = 0; gbc.gridy = row; JLabel phoneLabel = new JLabel("Phone Number:"); phoneLabel.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 24)); phoneLabel.setPreferredSize(new java.awt.Dimension(globalLabel, phoneLabel.getPreferredSize().height)); p.add(phoneLabel, gbc);
127    phoneField = new JTextField(18); phoneField.setName("contactlog_phone"); gbc.gridx = 1; p.add(phoneField, gbc);
128    row++;
129    gbc.gridx = 0; gbc.gridy = row; JLabel emailLabel = new JLabel("Email Address:"); emailLabel.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 24)); emailLabel.setPreferredSize(new java.awt.Dimension(globalLabel, emailLabel.getPreferredSize().height)); p.add(emailLabel, gbc);
130    emailField = new JTextField(24); emailField.setName("contactlog_email"); gbc.gridx = 1; p.add(emailField, gbc);
131    row++;
132    gbc.gridx = 0; gbc.gridy = row; JLabel responseLabel = new JLabel("Contact Response:"); responseLabel.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 24)); responseLabel.setPreferredSize(new java.awt.Dimension(globalLabel, responseLabel.getPreferredSize().height)); p.add(responseLabel, gbc);
133    contactResponseField = new JTextField(24); contactResponseField.setName("contactlog_response"); gbc.gridx = 1; p.add(contactResponseField, gbc);
134    row++;
135    gbc.gridx = 0; gbc.gridy = row; JLabel generalLabel = new JLabel("Contact General:"); generalLabel.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 24)); generalLabel.setPreferredSize(new java.awt.Dimension(globalLabel, generalLabel.getPreferredSize().height)); p.add(generalLabel, gbc);
136    contactGeneralField = new JTextField(24); contactGeneralField.setName("contactlog_general"); gbc.gridx = 1; p.add(contactGeneralField, gbc);
137    row++;
138    gbc.gridx = 0; gbc.gridy = row; JLabel specificLabel = new JLabel("Contact Specific:"); specificLabel.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 24)); specificLabel.setPreferredSize(new java.awt.Dimension(globalLabel, specificLabel.getPreferredSize().height)); p.add(specificLabel, gbc);
139    contactSpecificField = new JTextField(24); contactSpecificField.setName("contactlog_specific"); gbc.gridx = 1; p.add(contactSpecificField, gbc);
140    row++;
141
142    // Notes label + text area with accessibility
143    gbc.gridx = 0; gbc.gridy = row; gbc.gridwidth = 2; JLabel notesLabel = new JLabel("Notes:"); notesLabel.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 24)); notesLabel.setPreferredSize(new java.awt.Dimension(globalLabel, notesLabel.getPreferredSize().height)); p.add(notesLabel, gbc);
144    row++;
145
146    gbc.gridx = 0; gbc.gridy = row; gbc.gridwidth = 2; notesArea = new JTextArea(8,40); notesArea.setLineWrap(true); notesArea.setWrapStyleWord(true); notesArea.setToolTipText("Enter contact notes for the student"); notesArea.getAccessibleContext().setAccessibleName("Contact notes"); JScrollPane notesScroll = new JScrollPane(notesArea); notesScroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); p.add(notesScroll, gbc);
147    notesArea.setName("contactlog_notes");
148    notesLabel.setLabelFor(notesArea);
149
150    row++;
151    gbc.gridx = 0; gbc.gridy = row; gbc.gridwidth = 1; JButton save = new JButton("Save Contact");
152    save.addActionListener((ActionEvent e)-> saveContact());
153    save.setToolTipText("Save contact notes to the database");
154    save.setMnemonic(KeyEvent.VK_S);
155    save.getAccessibleContext().setAccessibleName("Save Contact Notes");
156    save.setName("contactlog_save");
157    p.add(save, gbc);
158
159    gbc.gridx = 1; JButton load = new JButton("Load Last Contact");
160    load.addActionListener((ActionEvent e) -> loadLastContact());
161    load.setToolTipText("Load the most recent contact for the selected student");
162    load.setName("contactlog_load");
163    p.add(load, gbc);
164
165    add(scroll, BorderLayout.CENTER);
166
167        SwingUtilities.invokeLater(()->{ p.setPreferredSize(p.getPreferredSize()); revalidate(); });
168
169        com.studentgui.apphelpers.Helpers.createFolderHierarchy();
170    }
171
172    private void loadLastContact() {
173        try {
174            com.studentgui.apphelpers.dto.ContactPayload p = com.studentgui.apphelpers.Database.fetchLatestContactLog(this.studentNameParam);
175            if (p == null) {
176                com.studentgui.apphelpers.UiNotifier.show("No contact found for this student.");
177                return;
178            }
179            guardianField.setText(p.guardian != null ? p.guardian : "");
180            String method = p.method != null ? p.method : "";
181            if (method != null) {
182                contactMethodCombo.setSelectedItem(method);
183            }
184            phoneField.setText(p.phone != null ? p.phone : "");
185            emailField.setText(p.email != null ? p.email : "");
186            contactResponseField.setText(p.response != null ? p.response : "");
187            contactGeneralField.setText(p.general != null ? p.general : "");
188            contactSpecificField.setText(p.specific != null ? p.specific : "");
189            notesArea.setText(p.notes != null ? p.notes : "");
190        } catch (SQLException ex) {
191            LOG.error("Error loading last contact", ex);
192            JOptionPane.showMessageDialog(this, "Database error loading contact: " + ex.getMessage(), "Database error", JOptionPane.ERROR_MESSAGE);
193        }
194    }
195
196    /**
197     * Persist the contact notes entered into the notes area as a session note
198     * for the selected student. Shows a confirmation dialog on success and
199     * error dialogs on failure.
200     */
201    private void saveContact() {
202        try {
203            int studentId = com.studentgui.apphelpers.Database.getOrCreateStudent(this.studentNameParam);
204            int ptId = com.studentgui.apphelpers.Database.getOrCreateProgressType("ContactLog");
205            int sessionId = com.studentgui.apphelpers.Database.createProgressSession(studentId, ptId, this.dateParam);
206
207            String notes = notesArea.getText();
208            String guardian = guardianField.getText();
209            String method = (String) contactMethodCombo.getSelectedItem();
210            String phone = phoneField.getText();
211            String email = emailField.getText();
212            String response = contactResponseField.getText();
213            String general = contactGeneralField.getText();
214            String specific = contactSpecificField.getText();
215
216            // Basic validation
217            if (method != null && method.equals("Email") && (email == null || !email.matches("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$"))) {
218                JOptionPane.showMessageDialog(this, "Please enter a valid email address.", "Validation", JOptionPane.WARNING_MESSAGE);
219                return;
220            }
221            if (method != null && method.equals("Phone") && (phone == null || !phone.matches("^[0-9+()\\-\s]{7,20}$"))) {
222                JOptionPane.showMessageDialog(this, "Please enter a valid phone number.", "Validation", JOptionPane.WARNING_MESSAGE);
223                return;
224            }
225
226            // Save both the free-form notes field on ProgressSession and structured ContactLog row
227            com.studentgui.apphelpers.Database.saveSessionNotes(sessionId, notes);
228            com.studentgui.apphelpers.Database.saveContactLog(sessionId, this.studentNameParam, this.dateParam.toString(), guardian, method, phone, email, response, general, specific, notes);
229            LOG.info("Saved contact log for {}", studentNameParam);
230            com.studentgui.apphelpers.UiNotifier.show("Contact log saved.");
231            com.studentgui.apphelpers.dto.ContactPayload payload = new com.studentgui.apphelpers.dto.ContactPayload(sessionId, guardian, method, phone, email, response, general, specific, notes);
232            java.nio.file.Path jsonOut = com.studentgui.apphelpers.SessionJsonWriter.writeSessionJson(this.studentNameParam, "ContactLog", payload, sessionId);
233            if (jsonOut == null) {
234                LOG.warn("Unable to save ContactLog session JSON for sessionId={}", sessionId);
235            }
236        } catch (SQLException ex) {
237            LOG.error("Error saving contact log", ex);
238            javax.swing.JOptionPane.showMessageDialog(this, "Database error saving contact log: " + ex.getMessage(), "Database error", javax.swing.JOptionPane.ERROR_MESSAGE);
239        }
240    }
241}