diff --git a/recorder/pom.xml b/recorder/pom.xml
index 3ae0933..a1241c4 100644
--- a/recorder/pom.xml
+++ b/recorder/pom.xml
@@ -25,6 +25,11 @@
             flatlaf
             3.1
         
+        
+            org.jfree
+            jfreechart
+            1.5.3
+        
     
 
 
\ No newline at end of file
diff --git a/recorder/src/main/java/com/github/andrewlalis/running_every_day/view/AddRunRecordDialog.java b/recorder/src/main/java/com/github/andrewlalis/running_every_day/view/AddRunRecordDialog.java
new file mode 100644
index 0000000..4e994fb
--- /dev/null
+++ b/recorder/src/main/java/com/github/andrewlalis/running_every_day/view/AddRunRecordDialog.java
@@ -0,0 +1,208 @@
+package com.github.andrewlalis.running_every_day.view;
+
+import com.github.andrewlalis.running_every_day.data.DataSource;
+import com.github.andrewlalis.running_every_day.data.RunRecord;
+
+import javax.swing.*;
+import java.awt.*;
+import java.math.BigDecimal;
+import java.sql.SQLException;
+import java.time.Duration;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+public class AddRunRecordDialog extends JDialog {
+	private final DataSource dataSource;
+
+	private final JTextField dateField;
+	private final JTextField timeField;
+	private final JTextField distanceField;
+	private final JTextField durationField;
+	private final JTextField weightField;
+	private final JTextArea commentField;
+
+	private boolean added = false;
+
+	public AddRunRecordDialog(Window owner, DataSource dataSource) {
+		super(owner, "Add a Record", ModalityType.APPLICATION_MODAL);
+
+		this.dataSource = dataSource;
+
+		this.dateField = new JTextField(LocalDate.now().toString(), 0);
+		this.timeField = new JTextField(LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm")), 0);
+		this.distanceField = new JTextField("5.0", 0);
+		this.durationField = new JTextField("00:00:00", 0);
+		this.weightField = new JTextField("85.0", 0);
+		this.commentField = new JTextArea(3, 0);
+
+		JPanel mainPanel = new JPanel(new BorderLayout());
+		JPanel contentPanel = new JPanel();
+		contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.PAGE_AXIS));
+		contentPanel.add(buildFieldsPanel());
+		mainPanel.add(contentPanel, BorderLayout.NORTH);
+
+		JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
+		JButton addButton = new JButton("Add");
+		addButton.addActionListener(e -> onAdd());
+		JButton cancelButton = new JButton("Cancel");
+		cancelButton.addActionListener(e -> this.dispose());
+		buttonPanel.add(addButton);
+		buttonPanel.add(cancelButton);
+		mainPanel.add(buttonPanel, BorderLayout.SOUTH);
+		this.setContentPane(mainPanel);
+
+		this.setPreferredSize(new Dimension(400, 300));
+		this.pack();
+		this.setLocationRelativeTo(owner);
+	}
+
+	public boolean isAdded() {
+		return added;
+	}
+
+	private Container buildFieldsPanel() {
+		JPanel panel = new JPanel(new GridBagLayout());
+		var c = new GridBagConstraints();
+		c.insets = new Insets(5, 5, 5, 5);
+		c.weightx = 0;
+		c.weighty = 0;
+		c.gridx = 0;
+		c.gridy = 0;
+		c.anchor = GridBagConstraints.NORTHWEST;
+		String[] labels = new String[]{"Date", "Start Time", "Distance (Km)", "Duration (HH:MM:SS)", "Weight (Kg)", "Comments"};
+		for (var label : labels) {
+			panel.add(new JLabel(label), c);
+			c.gridy++;
+		}
+		Component[] fields = new Component[]{dateField, timeField, distanceField, durationField, weightField, commentField};
+		c.weightx = 1;
+		c.weighty = 1;
+		c.gridx = 1;
+		c.gridy = 0;
+		c.anchor = GridBagConstraints.NORTHEAST;
+		for (var field : fields) {
+			if (field instanceof JTextArea) {
+				c.fill = GridBagConstraints.BOTH;
+			} else {
+				c.fill = GridBagConstraints.HORIZONTAL;
+			}
+			panel.add(field, c);
+			c.gridy++;
+		}
+		return panel;
+	}
+
+	private List validateForm() {
+		List errorMessages = new ArrayList<>();
+		LocalDate date = null;
+		try {
+			date = LocalDate.parse(dateField.getText());
+			if (date.isAfter(LocalDate.now())) {
+				errorMessages.add("Cannot add a run for a date in the future.");
+			}
+		} catch (DateTimeParseException e) {
+			errorMessages.add("Invalid date format. Should be YYYY-MM-DD.");
+		}
+		try {
+			var time = LocalTime.parse(timeField.getText());
+			if (date != null && date.atTime(time).isAfter(LocalDateTime.now())) {
+				errorMessages.add("Cannot add a run for a time in the future.");
+			}
+		} catch (DateTimeParseException e) {
+			errorMessages.add("Invalid start time format. Should be HH:MM.");
+		}
+
+		try {
+			BigDecimal distanceKm = new BigDecimal(distanceField.getText());
+			if (distanceKm.compareTo(BigDecimal.ZERO) < 0 || distanceKm.scale() > 3) {
+				errorMessages.add("Invalid distance.");
+			}
+		} catch (NumberFormatException e) {
+			errorMessages.add("Invalid or missing distance.");
+		}
+
+		try {
+			Duration duration = parseDuration(durationField.getText());
+			if (duration.isNegative() || duration.isZero()) {
+				errorMessages.add("Duration must be positive.");
+			}
+		} catch (IllegalArgumentException e) {
+			errorMessages.add(e.getMessage());
+		}
+
+		try {
+			BigDecimal weightKg = new BigDecimal(weightField.getText());
+			if (weightKg.compareTo(BigDecimal.ZERO) < 0 || weightKg.scale() > 3) {
+				errorMessages.add("Invalid weight.");
+			}
+		} catch (NumberFormatException e) {
+			errorMessages.add("Invalid or missing weight.");
+		}
+
+		if (commentField.getText().isBlank()) {
+			errorMessages.add("Comments should not be empty.");
+		}
+
+		return errorMessages;
+	}
+
+	private void onAdd() {
+		var messages = validateForm();
+		if (!messages.isEmpty()) {
+			JOptionPane.showMessageDialog(
+					this,
+					"Form validation failed:\n" + String.join("\n", messages),
+					"Validation Failed",
+					JOptionPane.WARNING_MESSAGE
+			);
+		} else {
+			try {
+				this.dataSource.runRecords().save(new RunRecord(
+						0,
+						LocalDate.parse(dateField.getText()),
+						LocalTime.parse(timeField.getText()),
+						new BigDecimal(distanceField.getText()),
+						parseDuration(durationField.getText()),
+						new BigDecimal(weightField.getText()),
+						commentField.getText().strip()
+				));
+				this.added = true;
+				this.dispose();
+			} catch (SQLException e) {
+				e.printStackTrace();
+				JOptionPane.showMessageDialog(
+						this,
+						"An SQL Exception occurred: " + e.getMessage(),
+						"Error",
+						JOptionPane.ERROR_MESSAGE
+				);
+			}
+
+		}
+	}
+
+	private static Duration parseDuration(String s) {
+		String[] parts = s.strip().split(":");
+		if (parts.length < 2 || parts.length > 3) {
+			throw new IllegalArgumentException("Invalid or missing duration.");
+		}
+		int hours = 0;
+		int minutes = 0;
+		int seconds = 0;
+		if (parts.length == 3) {
+			hours = Integer.parseInt(parts[0]);
+			minutes = Integer.parseInt(parts[1]);
+			seconds = Integer.parseInt(parts[2]);
+		} else {
+			minutes = Integer.parseInt(parts[0]);
+			seconds = Integer.parseInt(parts[1]);
+		}
+		return Duration.ofSeconds(hours * 60L * 60 + minutes * 60L + seconds);
+	}
+}
diff --git a/recorder/src/main/java/com/github/andrewlalis/running_every_day/view/ChartsPanel.java b/recorder/src/main/java/com/github/andrewlalis/running_every_day/view/ChartsPanel.java
new file mode 100644
index 0000000..b23af7f
--- /dev/null
+++ b/recorder/src/main/java/com/github/andrewlalis/running_every_day/view/ChartsPanel.java
@@ -0,0 +1,48 @@
+package com.github.andrewlalis.running_every_day.view;
+
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.JFreeChart;
+import org.jfree.data.category.DefaultCategoryDataset;
+import org.jfree.data.time.Day;
+import org.jfree.data.time.TimeSeries;
+import org.jfree.data.time.TimeSeriesCollection;
+import org.jfree.data.xy.XYDataset;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.geom.Rectangle2D;
+
+public class ChartsPanel extends JPanel {
+	private final JPanel drawingPanel = new JPanel();
+
+	public ChartsPanel() {
+		super(new BorderLayout());
+		this.add(drawingPanel, BorderLayout.CENTER);
+
+		JPanel buttonPanel = new JPanel();
+		JButton drawButton = new JButton("Draw");
+		drawButton.addActionListener(e -> draw());
+		buttonPanel.add(drawButton);
+		this.add(buttonPanel, BorderLayout.NORTH);
+	}
+
+	public void draw() {
+		TimeSeriesCollection ds = new TimeSeriesCollection();
+		TimeSeries ts = new TimeSeries("Data");
+		ts.add(new Day(16, 4, 2023), 45);
+		ts.add(new Day(17, 4, 2023), 50);
+		ts.add(new Day(18, 4, 2023), 52);
+		ts.add(new Day(19, 4, 2023), 65);
+		ds.addSeries(ts);
+
+		JFreeChart chart = ChartFactory.createXYLineChart(
+				"Test XY Line Chart",
+				"Date",
+				"Value",
+				ds
+		);
+		Graphics2D g2 = (Graphics2D) drawingPanel.getGraphics();
+		Rectangle2D area = new Rectangle2D.Float(0, 0, drawingPanel.getWidth(), drawingPanel.getHeight());
+		chart.draw(g2, area);
+	}
+}
diff --git a/recorder/src/main/java/com/github/andrewlalis/running_every_day/view/RecorderAppWindow.java b/recorder/src/main/java/com/github/andrewlalis/running_every_day/view/RecorderAppWindow.java
index 9400c8e..780a1fd 100644
--- a/recorder/src/main/java/com/github/andrewlalis/running_every_day/view/RecorderAppWindow.java
+++ b/recorder/src/main/java/com/github/andrewlalis/running_every_day/view/RecorderAppWindow.java
@@ -18,16 +18,8 @@ public class RecorderAppWindow extends JFrame {
     private Container buildGui(DataSource dataSource) {
         JTabbedPane tabbedPane = new JTabbedPane();
         tabbedPane.addTab("Run Records", new RunRecordsPanel(dataSource));
-        tabbedPane.addTab("Aggregate Statistics", buildAggregateStatisticsPanel(dataSource));
-        tabbedPane.addTab("Charts", buildChartsPanel(dataSource));
+        tabbedPane.addTab("Aggregate Statistics", new JPanel());
+        tabbedPane.addTab("Charts", new ChartsPanel());
         return tabbedPane;
     }
-
-    private Container buildAggregateStatisticsPanel(DataSource dataSource) {
-        return new JPanel();
-    }
-
-    private Container buildChartsPanel(DataSource dataSource) {
-        return new JPanel();
-    }
 }
diff --git a/recorder/src/main/java/com/github/andrewlalis/running_every_day/view/RunRecordTableModel.java b/recorder/src/main/java/com/github/andrewlalis/running_every_day/view/RunRecordTableModel.java
index 078be60..c59157f 100644
--- a/recorder/src/main/java/com/github/andrewlalis/running_every_day/view/RunRecordTableModel.java
+++ b/recorder/src/main/java/com/github/andrewlalis/running_every_day/view/RunRecordTableModel.java
@@ -68,7 +68,7 @@ public class RunRecordTableModel extends AbstractTableModel {
         return currentPage;
     }
 
-    private void loadPage() {
+    public void loadPage() {
         try {
             var page = dataSource.runRecords().findAll(currentPage);
             cachedPageCount = (int) dataSource.runRecords().pageCount(currentPage.size());
diff --git a/recorder/src/main/java/com/github/andrewlalis/running_every_day/view/RunRecordsPanel.java b/recorder/src/main/java/com/github/andrewlalis/running_every_day/view/RunRecordsPanel.java
index 07297ca..d8ee9e6 100644
--- a/recorder/src/main/java/com/github/andrewlalis/running_every_day/view/RunRecordsPanel.java
+++ b/recorder/src/main/java/com/github/andrewlalis/running_every_day/view/RunRecordsPanel.java
@@ -28,8 +28,12 @@ public class RunRecordsPanel extends JPanel {
         table.getColumnModel().getColumn(1).setMaxWidth(80);
         table.getColumnModel().getColumn(2).setMaxWidth(80);
         table.getColumnModel().getColumn(3).setMaxWidth(100);
+        table.getColumnModel().getColumn(3).setPreferredWidth(100);
         table.getColumnModel().getColumn(4).setMaxWidth(80);
         table.getColumnModel().getColumn(5).setMaxWidth(80);
+        for (int i = 0; i < 6; i++) {
+            table.getColumnModel().getColumn(i).setResizable(false);
+        }
         var scrollPane = new JScrollPane(table, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
         this.add(scrollPane, BorderLayout.CENTER);
 
@@ -86,6 +90,13 @@ public class RunRecordsPanel extends JPanel {
 
         JPanel actionsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
         JButton addActionButton = new JButton("Add a Record");
+        addActionButton.addActionListener(e -> {
+            var dialog = new AddRunRecordDialog(SwingUtilities.getWindowAncestor(this), dataSource);
+            dialog.setVisible(true);
+            if (dialog.isAdded()) {
+                tableModel.firstPage();
+            }
+        });
         actionsPanel.add(addActionButton);
         this.add(actionsPanel, BorderLayout.NORTH);
     }