Driver Drawer

Comprehensive drawer component for creating, viewing, and editing drivers with project access management

Live Example

Create New Driver

Open the drawer to create a new driver with name, username, password, and organization code.

View & Edit Existing Driver

Select a driver to view their details. In view mode, you can click Edit to modify the driver's information, manage project access, or change their password.

Select a driver to view and edit:

Features Overview

The DriverDrawer component supports multiple modes and features.

Modes:

  • Create Mode: Create new drivers with credentials
  • View Mode: Read-only display with Edit and Change Password buttons
  • Edit Mode: Modify driver info and manage project access

Features:

  • Project access management (participation-type only)
  • Password change with validation
  • Username splitting (editable@readonly)
  • Loading indicators for all operations
  • Full localization support (EN/JA)
  • Dual-API pattern for data consistency

Driver Drawer

A full-featured drawer component for driver management. Supports creating new drivers, viewing driver details, editing driver information, managing project access, and changing passwords.

Features

  • Three Modes: Create, View, and Edit modes with appropriate UI states
  • Driver Management: Create new drivers or edit existing ones (name, username, password)
  • Project Access Control: Assign and remove participation-type projects for drivers
  • Password Management: Change password functionality with validation
  • Username Handling: Smart username display splitting organization code from editable username
  • Dual-API Pattern: Writes to Tastypie API, refreshes from GET API for consistency
  • Collection Integration: Automatically updates driver and projectmember collections
  • Loading States: Comprehensive loading indicators for all operations
  • Error Handling: Clear error messages for validation and API failures
  • Localization: Full English and Japanese translation support

Usage

Create New Driver

import { DriverDrawer } from '@/components/sgerp/driver-drawer';
import { useSGERP } from 'sgerp-frontend-lib';
import { useState } from 'react';

function MyComponent() {
  const api = useSGERP();
  const [drawerOpen, setDrawerOpen] = useState(false);

  return (
    <>
      <button onClick={() => setDrawerOpen(true)}>
        Create Driver
      </button>

      <DriverDrawer
        open={drawerOpen}
        onOpenChange={setDrawerOpen}
        api={api}
        onSuccess={(result) => {
          console.log('Driver created:', result);
          setDrawerOpen(false);
        }}
      />
    </>
  );
}

View Existing Driver

import { DriverDrawer } from '@/components/sgerp/driver-drawer';
import { useSGERP } from 'sgerp-frontend-lib';
import { useState } from 'react';

function MyComponent() {
  const api = useSGERP();
  const [selectedDriverId, setSelectedDriverId] = useState<number | undefined>();
  const [drawerOpen, setDrawerOpen] = useState(false);

  const handleRowClick = (driverId: number) => {
    setSelectedDriverId(driverId);
    setDrawerOpen(true);
  };

  return (
    <>
      {/* Your driver table/list here */}

      <DriverDrawer
        open={drawerOpen}
        onOpenChange={(open) => {
          if (!open) setSelectedDriverId(undefined);
          setDrawerOpen(open);
        }}
        driverId={selectedDriverId}
        api={api}
        onSuccess={(result) => {
          console.log('Driver updated:', result);
        }}
      />
    </>
  );
}

With DriverTable Integration

import { DriverTable } from '@/components/sgerp/tables/driver-table';
import { DriverDrawer } from '@/components/sgerp/driver-drawer';
import { useSGERP } from 'sgerp-frontend-lib';
import { useState } from 'react';

function DriversPage() {
  const api = useSGERP();
  const [drawerOpen, setDrawerOpen] = useState(false);
  const [selectedDriverId, setSelectedDriverId] = useState<number | undefined>();

  const handleRowClick = (driver: Driver) => {
    setSelectedDriverId(driver.id);
    setDrawerOpen(true);
  };

  const handleDrawerOpenChange = (open: boolean) => {
    if (!open) setSelectedDriverId(undefined);
    setDrawerOpen(open);
  };

  return (
    <div>
      <button onClick={() => {
        setSelectedDriverId(undefined);
        setDrawerOpen(true);
      }}>
        Create Driver
      </button>

      <DriverTable
        collection={api?.collections.driver ?? null}
        onRowClick={handleRowClick}
      />

      <DriverDrawer
        open={drawerOpen}
        onOpenChange={handleDrawerOpenChange}
        driverId={selectedDriverId}
        api={api}
        onSuccess={(result) => {
          // Refresh driver list if needed
          api?.collections.driver.fetch({});
        }}
      />
    </div>
  );
}

Props

PropTypeRequiredDescription
openbooleanYesControls drawer open state
onOpenChange(open: boolean) => voidYesCallback when drawer open state changes
driverIdnumberNoID of driver to view/edit (omit for create mode)
initialDataDriverNoPre-loaded driver data to avoid fetching
apiReturnType<typeof useSGERP>NoShared API instance (highly recommended)
onSuccess(result: { user_id: number; driver_id: number }) => voidNoCallback when create/update succeeds

Modes

Create Mode

  • Triggered when driverId is not provided
  • Shows form for creating new driver with name, username, password
  • Organization code required for username format

View Mode

  • Triggered when driverId is provided
  • Shows read-only driver information
  • Displays assigned projects
  • Shows Edit button to switch to Edit mode
  • Shows Change Password button

Edit Mode

  • Triggered by clicking Edit button in View mode
  • Allows editing name and username (organization code read-only)
  • Manages project access (add/remove projects)
  • Shows Save and Close buttons

Form Fields

Create Mode Fields

  • Driver Name: Full name of the driver (required)
  • Username: Username part before @ (required)
  • Password: Initial password (required, min 8 characters)
  • Organization Code: Organization identifier (required)

Edit Mode Fields

  • Driver ID: Non-editable reference ID
  • User ID: Non-editable user reference ID
  • Driver Name: Editable full name
  • Username: Editable username part (organization code read-only)
  • Projects: List of assigned projects with add/remove capabilities

Project Management

The driver drawer includes comprehensive project access management:

Features

  • Loads only participation-type projects from driver's organization
  • Displays currently assigned projects with organization info
  • Add projects via dropdown (shows "already granted" for assigned projects)
  • Remove project access with confirmation
  • Loading indicators for add/remove operations
  • Automatic collection updates

Project Filters

Only shows participation-type projects: project__organizations__type=participation

Example Project Management Flow

// User opens driver in view mode
// Clicks "Edit" button
// In "Projects" section:
//   - See list of assigned projects
//   - Click "Remove" to revoke access
//   - Use dropdown to add new projects
// Click "Save" to persist changes

Password Management

Change Password Feature

  • Available in View and Edit modes
  • Expandable form with password and confirm password fields
  • Validation: Password required, passwords must match
  • Updates via PATCH to /api/v2/user/{user_id} with set_password
  • Clears form on success

Example

// In view/edit mode:
// Click "Change Password" button
// Form expands with:
//   - New Password input
//   - Confirm New Password input
//   - Update Password button (with spinner)
//   - Cancel button

Username Display Logic

Usernames follow the format username@organization_code:

Create Mode

  • User enters username and organization code separately
  • Combined on save: ${username}@${organizationCode}

View/Edit Mode

  • Username split on @ character
  • Editable part: username (before @)
  • Read-only part: organization_code (after @)
  • Reconstructed on save

Dual-API Implementation

The drawer follows the dual-API pattern:

Create Driver

  1. POST to /api/v2/driver/ (Tastypie API) with user and driver data
  2. Refresh from GET API to get consistent format
  3. Update driver collection using add()

Edit Driver

  1. PATCH to /api/v2/user/{user_id} for name and username
  2. PATCH to /api/v2/driver/{driver_id} for driver name
  3. GET fresh data from microservices API
  4. Merge with existing nested data (user, projectMembers)
  5. Update collection using add() to preserve other models

Project Access

  1. Add: POST to /api/v2/projectmember/ with resource URIs
  2. Remove: DELETE to /api/v2/projectmember/{id}/
  3. Refresh from GET API
  4. Update both projectmember and driver collections

Loading States

The drawer provides loading indicators for:

  • Initial driver data fetch
  • Projects data fetch
  • Save operation (Create/Update)
  • Add project operation
  • Remove project operation (per-project spinner)
  • Password change operation

Error Handling

Comprehensive error handling for:

  • Network errors with API messages
  • Validation errors (required fields, password mismatch)
  • Loading errors (driver not found)
  • Project operation errors

Data Preservation

IMPORTANT: The drawer uses collection.add() instead of collection.fetch() after updates to preserve all rows in parent tables and maintain nested data structures.

Why This Matters

  • fetch({ id }) calls reset() which clears all other models from collection
  • add([data]) merges by ID without clearing other models
  • Nested data (user, projectMembers) is preserved from existing driver

Localization

Fully localized with keys in sgerplib/locales/:

  • driver.create_driver, driver.edit_driver, driver.view_driver
  • driver.projects, driver.add_project_placeholder, driver.no_projects
  • driver.change_password, driver.new_password, driver.confirm_new_password
  • All error messages and form labels

Styling

Built with:

  • Radix UI Sheet component for drawer
  • Tailwind CSS for styling
  • Lucide React icons
  • Theme-aware colors