diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 76c298c..2e2a41d 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -14,7 +14,7 @@ jobs: fetch-depth: 0 - uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20 - name: Install dependencies run: npm install diff --git a/.github/workflows/test-deploy.yml b/.github/workflows/test-deploy.yml index 3be2c53..ba2b457 100644 --- a/.github/workflows/test-deploy.yml +++ b/.github/workflows/test-deploy.yml @@ -17,7 +17,7 @@ jobs: fetch-depth: 0 - uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20 - name: Install dependencies run: npm install diff --git a/docs/learn/introduction.mdx b/docs/learn/introduction.mdx new file mode 100644 index 0000000..907b55a --- /dev/null +++ b/docs/learn/introduction.mdx @@ -0,0 +1,78 @@ +--- +sidebar_position: 0 +description: Learn to build an offline-first fisheries data collection app with RADFish +title: Introduction +--- + +# Learn RADFish + +Welcome to the RADFish tutorial! In this comprehensive guide, you'll learn how to build a complete **Fisheries Trip Logging Application** using RADFish, NOAA's React framework for offline-first data collection. + +## The App We're Building + +Throughout this tutorial, you'll build a fisheries data collection application that commercial fishermen can use to log their trips and catches. This Progressive Web App (PWA) works offline and syncs data when connectivity is restored. + +![Fisheries Trip Logging Application](./src/assets/intro.png) + +### Key Features You'll Implement + +- **🚢 Trip Management**: Start and end fishing trips with date, time, and weather conditions +- **🐟 Catch Logging**: Record detailed information about each fish caught (species, weight, length, coordinates, time) +- **📝 Dynamic Forms**: Build interactive forms with repeatable catch entry +- **📊 Data Review**: Display aggregated catch statistics organized by species using tables +- **📱 Offline-First**: Full functionality without internet connection using IndexedDB +- **🔄 Server Sync**: Send data to backend APIs when online using JSON server +- **⚡ Progressive Web App**: Offline-first and mobile-optimized + +## What You'll Learn + +By the end of this tutorial, you'll understand how to: + +### RADFish Core Concepts +- **Offline Storage**: Use IndexedDB with RADFish schemas and collections +- **State Management**: Manage application state with RADFish hooks +- **Data Relationships**: Link related data across collections (trips and catches) + +### React & USWDS Integration +- **Component Architecture**: Build accessible forms using Trussworks React USWDS components +- **Navigation**: Implement multi-step workflows with React Router +- **Form Validation**: Implement client-side validation with user-friendly error handling +- **Responsive Design**: Create mobile-first layouts following federal design standards + +### Progressive Web App Features +- **Network Detection**: Provide dynamic UI feedback based on connectivity using `useOfflineStatus` +- **Data Synchronization**: Handle online/offline data persistence and sync patterns + +## Learning Path + +This tutorial is divided into 7 progressive lessons: + +1. **Page Structure & Navigation** - Set up USWDS grid layouts and routing +2. **Start Trip Form Inputs** - Build date pickers, time pickers, and select components +3. **Form Submission** - Save data to offline storage with RADFish +4. **Dynamic Inputs** - Create repeatable catch logging forms with collection relationships +5. **Form Validation** - Implement validation rules and error handling across all forms +6. **Review & Tables** - Display aggregated data with computed fields and offline detection +7. **Server Submission** - Handle online/offline data synchronization with JSON server + +Each lesson builds upon the previous one, gradually introducing new RADFish concepts and React patterns. + +## Prerequisites + +Before starting this tutorial, you should have: + +- **React Knowledge**: Basic understanding of React components, hooks, and state management +- **JavaScript Fundamentals**: ES6+ syntax, async/await, and modern JavaScript concepts +- **Terminal Familiarity**: Comfortable running npm commands and navigating file systems + +No prior experience with RADFish, PWAs, or offline-first development is required - we'll teach you everything you need to know! + +## Ready to Start? + +This tutorial takes a hands-on approach. You'll be writing code from the very first lesson, building features incrementally while learning RADFish concepts along the way. + +The completed application demonstrates patterns designed for NOAA fisheries applications, making this tutorial valuable whether you're building data collection apps for scientific research, regulatory compliance, or commercial fishing operations. + +--- + +*Having trouble? Check out our [developer documentation](/developer-documentation/getting-started) for additional RADFish resources and troubleshooting guides.* \ No newline at end of file diff --git a/docs/learn/lesson-1.mdx b/docs/learn/lesson-1.mdx new file mode 100644 index 0000000..f536369 --- /dev/null +++ b/docs/learn/lesson-1.mdx @@ -0,0 +1,170 @@ +import CodeBlock from "@theme/CodeBlock"; + +# Lesson 1: Page Structure and Navigation + +Welcome to the RADFish tutorial! In this first lesson, we'll structure our main page using USWDS grid components and then implement navigation within the app by making the "Start New Trip" button functional. + +## Prerequisites + +Before you start, make sure you have the following installed: + +- **Node.js** (v20.17.0 or later) +- **npm** (v10.8.2 or later) +- **git** (v2.0 or later) + +You can verify your installations by running these commands in your terminal: + +```bash +node --version +npm --version +git --version +``` + +## Getting Started + +Clone the repository to your local machine and navigate to the project directory: + +```bash +git clone https://github.com/NMFS-RADFish/learn-radfish.git +cd learn-radfish +``` + +Now let's build the application and serve it locally to see its current state. + +1. **Install Dependencies:** Open your terminal in the project root directory and run: + ```bash + npm install + ``` + +2. **Start the Development Server:** Run the following command to start the application: + ```bash + npm run start + ``` + This command will start the development server and make the application available at [http://localhost:3000](http://localhost:3000). Open this URL in your browser. + +## Step 1: Structure the Page with USWDS Grid Components + +We'll begin by structuring our `HomePage` using `GridContainer` and `Grid` components from the U.S. Web Design System (USWDS). This is a common practice in RADFish applications to ensure consistent layout, responsiveness, and alignment with USWDS standards. These components help center content and manage its maximum width. + +For more detailed information on the grid components we are using, see these resources: + +- **[Trussworks React USWDS Grid documentation](https://trussworks.github.io/react-uswds/?path=/docs/components-grid--docs)** - Detailed information on the React components (`GridContainer`, `Grid`) from `@trussworks/react-uswds` +- **[Official USWDS Layout Grid documentation](https://designsystem.digital.gov/utilities/layout-grid/)** - Understanding the foundational CSS grid system and principles upon which these components are built + +### 1.1: Import Grid Components + +In `src/pages/Home.jsx`, update your import statement from `@trussworks/react-uswds` to also include `GridContainer`. Your existing import for `Button` and `Grid` will be modified as follows: + +```jsx title="src/pages/Home.jsx" showLineNumbers=5 +import "../index.css"; +import React, { useState, useEffect } from "react"; +import { useApplication, useOfflineStatus } from "@nmfs-radfish/react-radfish"; +import { useNavigate } from "react-router-dom"; +//diff-remove-start +import { Button, Grid } from "@trussworks/react-uswds"; +//diff-remove-end +//diff-add-start +import { Button, GridContainer, Grid } from "@trussworks/react-uswds"; +//diff-add-end +``` + +### 1.2: Apply the Grid Structure + +Next, modify the `return` statement of your `HomePage` component to incorporate the USWDS grid. You will wrap your main page content (everything inside the main `<>...` fragment, except for the sticky footer) with these components. + +**Add the `GridContainer` structure around your main page content:** + +```jsx title="src/pages/Home.jsx" +return ( + <> + {/* Main Content - Basic structure without USWDS grid components */} + //diff-remove-start +
+ //diff-remove-end + //diff-add-start + + + + //diff-add-end + {/* All your main page content (headings, lists, etc.) goes here */} +

Hi, Captain

+

+ Recent Trips +

+ {/* Example: trips.map(...) or conditional messages */} + //diff-remove-start +
+ //diff-remove-end + //diff-add-start + + + + //diff-add-end + + {/* Sticky footer remains outside the GridContainer */} + + +); +``` + +**Why `GridContainer`?** + +The `GridContainer` component from USWDS is crucial for maintaining a consistent layout. It centers your content on the page and applies a maximum width (e.g., 1024px on desktop by default), preventing content from stretching uncomfortably wide on larger screens. The `Grid row` serves as a necessary container for grid columns, and `Grid col="fill"` allows the column to expand and take up the available space within that row. + +## Step 2: Implement "Start New Trip" Navigation + +Currently, the "Start New Trip" button on the home page doesn't do anything. We need to make it navigate the user to the first step of the trip logging process, which will be at the `/start` route. We'll use the `useNavigate` hook from `react-router-dom` for this. + +### Understanding React Router Navigation + +React Router provides the `useNavigate` hook for programmatic navigation. Let's look at how it's already set up in your component: + +**1. Import Statement:** + +```jsx title="src/pages/Home.jsx" +import { useNavigate } from "react-router-dom"; +``` + +**2. Hook Initialization:** + +```jsx title="src/pages/Home.jsx" +const navigate = useNavigate(); +``` + +The `useNavigate` hook returns a function that we store in the `navigate` variable. This function allows us to programmatically change the current route (URL) in our application. + +### 2.1: Add the Navigation Handler + +Now we'll use the `navigate` function to make the "Start New Trip" button functional. Find the `Button` component for "Start New Trip". + +**Add the `onClick` handler code:** + +```jsx title="src/pages/Home.jsx" + +``` + +**Explanation:** + +- We're adding an `onClick` event handler to the `Button`. +- Inside the handler, we call the `navigate` function (which we get from the `useNavigate()` hook provided earlier in the component). +- We pass the string `"/start"` to `navigate`, telling React Router to change the URL to `/start` when the button is clicked. This will cause the `App` component to render the `StartTrip` page component associated with that route. + +**Test Navigation:** + +- Click the "Start New Trip" button. You should now be navigated to a new page (which is currently blank or basic, as we haven't built the `StartTrip` form yet). + +## Conclusion + +In this lesson, you learned how to structure your page using USWDS `GridContainer` and `Grid` components for a consistent layout. You also implemented the first navigation feature, linking the home page button to the start of the trip logging workflow using React Router. The application now has a better-structured main page and a functional starting point for logging a new trip. diff --git a/docs/learn/lesson-2.mdx b/docs/learn/lesson-2.mdx new file mode 100644 index 0000000..023e7a9 --- /dev/null +++ b/docs/learn/lesson-2.mdx @@ -0,0 +1,395 @@ +# Lesson 2: Start Trip Form Inputs + +
+ 🏳️ Haven't completed the previous lesson? + + No worries! You can pickup from here: + + ```bash + git checkout tags/lesson-2 + ``` +
+ +This lesson focuses on building the first step of the trip logging form, the "Start Trip" page. We'll use components from the U.S. Web Design System (USWDS) library (`@trussworks/react-uswds`) to add inputs for the trip date, start time, and start weather. + +## Step 1: Add Date Picker Input + +We need a way for the user to select the date of their fishing trip. We'll use the `DatePicker` component from `@trussworks/react-uswds`. + +### 1.1: Import the DatePicker Component + +First, open `src/pages/StartTrip.jsx` and update the import statement from `@trussworks/react-uswds` to include `DatePicker`. Your existing import will be modified as follows: + +```jsx title="src/pages/StartTrip.jsx" +import { + Button, + //diff-add-start + DatePicker, + //diff-add-end + Form, + //diff-add-start + FormGroup, + //diff-add-end + //diff-add-start + Label, + //diff-add-end +} from "@trussworks/react-uswds"; +import Layout from "../components/Layout"; +``` + +:::info Layout Component + +Notice that we're importing a `Layout` component. This is a custom component that handles the common page structure for all our trip logging pages. It provides: + +- **Grid Container**: Wraps content with consistent padding and responsive width +- **Step Indicator**: Shows the user's progress through the 4-step trip logging process +- **Consistent Structure**: Ensures all pages have the same layout without duplicating code + +Each page uses this component with a `currentStep` prop (e.g., ``) to highlight the appropriate step in the indicator. This approach keeps our code DRY (Don't Repeat Yourself) and makes maintenance easier. +::: + +**Understanding the USWDS Components:** + +Before we start building the form, let's understand what each of these imported components does: + +- **`Form`**: Main container for form elements with submission handling and styling +- **`FormGroup`**: Wrapper that groups related form elements (label, input, hints, errors) for proper spacing and accessibility. Each input field needs its own FormGroup wrapper. +- **`Label`**: Creates accessible form labels with support for required indicators +- **`DatePicker`**: Calendar interface for date selection with formatting and validation + +These components work together to create accessible, well-structured forms that follow USWDS design patterns and accessibility guidelines. + +### 1.2: Add the DatePicker to the Form + +**Add the `FormGroup`, `Label`, and `DatePicker` input within the `Form` component:** + +```jsx title="src/pages/StartTrip.jsx" +
+ // diff-add-start + + + + + Please enter or select the date of your fishing trip. + + + // diff-add-end +
+``` + +#### React State Management Explanation + +Let's look at two key aspects of how React manages state with this form: + +1. **Initial State with `defaultValue`**: + + ```jsx title="src/pages/StartTrip.jsx" + defaultValue={tripData.tripDate} + ``` + + - The component initializes form data using React's useState hook: + + ```jsx title="src/pages/StartTrip.jsx" + const [tripData, setTripData] = useState({ + tripDate: undefined, + startWeather: undefined, + startTime: undefined, + }); + ``` + + - This binds the DatePicker's initial value to the component's state + - When the form first renders, it displays any existing data if editing a trip, or empty if creating a new trip + - To learn more about React state management, visit [React's Managing State documentation](https://react.dev/learn/managing-state) + +2. **Updating State with `onChange`**: + + ```jsx title="src/pages/StartTrip.jsx" + onChange = { handleDateChange }; + ``` + + - The `onChange` attribute is a required prop for controlled inputs. It is called whenever the input's value changes (on every keystroke or selection). Without this handler, React would revert the input to its original value after each change. + - When a user selects a date, the `handleDateChange` function is called + + ```jsx title="src/pages/StartTrip.jsx" + const handleDateChange = (date) => { + setTripData((prevData) => ({ ...prevData, tripDate: date || "" })); + }; + ``` + + - It preserves other form field values by spreading the previous state + - To learn more about controlled inputs, visit [Input documentation](https://react.dev/reference/react-dom/components/input#controlling-an-input-with-a-state-variable) + +## Step 2: Add Time Picker Input + +Next, we'll add an input for the trip's start time using the `TimePicker` component. + +### 2.1: Import the TimePicker Component + +Update the import statement from `@trussworks/react-uswds` to include `TimePicker`. Your existing import will be modified as follows: + +```jsx title="src/pages/StartTrip.jsx" +import { + Button, + DatePicker, + Form, + FormGroup, + Label, + //diff-add-start + TimePicker, + //diff-add-end +} from "@trussworks/react-uswds"; +``` + +### 2.2: Add the TimePicker to the Form + +Let's add the `TimePicker` input below the `DatePicker` form group: + +```jsx title="src/pages/StartTrip.jsx" + + Please enter or select the date of your fishing trip. + + + // diff-add-start + + + + + Please enter or select the time you started fishing. + + + // diff-add-end + +
+``` + +**Explanation:** + +- Similar to the `DatePicker`, we use `FormGroup` and `Label` for structure and accessibility +- `defaultValue={tripData.startTime}` binds the input to the `startTime` field in our state +- `onChange={handleTimeChange}` calls our specific handler function to update the state +- `minTime`, `maxTime`, and `step={15}` configure the available time options (from 00:00 to 23:45 in 15-minute increments) + +## Step 3: Add Weather Select Input + +Finally, we need a dropdown menu for the user to select the weather conditions at the start of the trip. We'll use the `Select` component. + +### 3.1: Import the Select Component + +Update the import statement from `@trussworks/react-uswds` to include `Select`. Your existing import will be modified as follows: + +```jsx title="src/pages/StartTrip.jsx" +import { + Button, + DatePicker, + Form, + FormGroup, + Label, + //diff-add-start + Select, + //diff-add-end + TimePicker, +} from "@trussworks/react-uswds"; +``` + +### 3.2: Add the Select to the Form + +Add the following code below the `TimePicker` form group: + +```jsx title="src/pages/StartTrip.jsx" + + Please enter or select the time you started fishing. + + + // diff-add-start + + + + + Please select the weather conditions at the start of your fishing trip. + + + // diff-add-end + + +``` + +**Explanation:** + +- `value={tripData.startWeather}` binds the selected option to the `startWeather` field in the state. +- `onChange={handleInputChange}` uses the standard input handler (already present) because the `Select` component behaves like a standard HTML select element in this regard. +- We define the available weather options using standard HTML `