Files
Laca-City/DEVELOPMENT.md
PhongPham c65cc97a33 🎯 MapView v2.0 - Global Deployment Ready
 MAJOR FEATURES:
• Auto-zoom intelligence với smart bounds fitting
• Enhanced 3D GPS markers với pulsing effects
• Professional route display với 6-layer rendering
• Status-based parking icons với availability indicators
• Production-ready build optimizations

🗺️ AUTO-ZOOM FEATURES:
• Smart bounds fitting cho GPS + selected parking
• Adaptive padding (50px) cho visual balance
• Max zoom control (level 16) để tránh quá gần
• Dynamic centering khi không có selection

🎨 ENHANCED VISUALS:
• 3D GPS marker với multi-layer pulse effects
• Advanced parking icons với status colors
• Selection highlighting với animation
• Dimming system cho non-selected items

🛣️ ROUTE SYSTEM:
• OpenRouteService API integration
• Multi-layer route rendering (glow, shadow, main, animated)
• Real-time distance & duration calculation
• Visual route info trong popup

📱 PRODUCTION READY:
• SSR safe với dynamic imports
• Build errors resolved
• Global deployment via Vercel
• Optimized performance

🌍 DEPLOYMENT:
• Vercel: https://whatever-ctk2auuxr-phong12hexdockworks-projects.vercel.app
• Bundle size: 22.8 kB optimized
• Global CDN distribution
• HTTPS enabled

💾 VERSION CONTROL:
• MapView-v2.0.tsx backup created
• MAPVIEW_VERSIONS.md documentation
• Full version history tracking
2025-07-20 19:52:16 +07:00

18 KiB

🛠️ Development Guide

This guide covers the development workflow, coding standards, and best practices for the Smart Parking Finder application.

📋 Table of Contents

  1. Development Setup
  2. Project Structure
  3. Development Workflow
  4. Coding Standards
  5. Testing Strategy
  6. Debugging
  7. Performance Guidelines
  8. Contributing

🚀 Development Setup

Prerequisites

  • Node.js 18+ and npm
  • Docker and Docker Compose
  • Git
  • VS Code (recommended)

Initial Setup

# 1. Clone repository
git clone <repository-url>
cd smart-parking-finder

# 2. Run automated setup
./setup.sh

# 3. Start development environment
docker-compose up -d

# 4. Install dependencies
cd frontend && npm install
cd ../backend && npm install

# 5. Start development servers
npm run dev:all  # Starts both frontend and backend

Environment Configuration

# .env.development
NODE_ENV=development

# Database
DATABASE_URL=postgresql://parking_user:parking_pass@localhost:5432/parking_db

# Redis
REDIS_URL=redis://localhost:6379

# Valhalla
VALHALLA_URL=http://localhost:8002

# Development
DEBUG=true
LOG_LEVEL=debug
HOT_RELOAD=true

📁 Project Structure

smart-parking-finder/
├── frontend/                 # Next.js frontend application
│   ├── src/
│   │   ├── app/             # App router pages
│   │   ├── components/      # Reusable UI components
│   │   ├── hooks/          # Custom React hooks
│   │   ├── services/       # API service layers
│   │   ├── types/          # TypeScript type definitions
│   │   └── utils/          # Utility functions
│   ├── public/             # Static assets
│   └── tests/              # Frontend tests
├── backend/                 # NestJS backend application
│   ├── src/
│   │   ├── modules/        # Feature modules
│   │   ├── common/         # Shared utilities
│   │   ├── config/         # Configuration files
│   │   └── database/       # Database related files
│   └── tests/              # Backend tests
├── valhalla/               # Routing engine setup
├── scripts/                # Development scripts
└── docs/                   # Documentation

Frontend Architecture

frontend/src/
├── app/                    # Next.js 14 App Router
│   ├── (dashboard)/       # Route groups
│   ├── api/              # API routes
│   ├── globals.css       # Global styles
│   ├── layout.tsx        # Root layout
│   └── page.tsx          # Home page
├── components/           # UI Components
│   ├── ui/              # Base UI components
│   ├── forms/           # Form components
│   ├── map/             # Map-related components
│   └── parking/         # Parking-specific components
├── hooks/               # Custom hooks
│   ├── useGeolocation.ts
│   ├── useParking.ts
│   └── useRouting.ts
├── services/            # API services
│   ├── api.ts           # Base API client
│   ├── parkingService.ts
│   └── routingService.ts
└── types/               # TypeScript definitions
    ├── parking.ts
    ├── routing.ts
    └── user.ts

Backend Architecture

backend/src/
├── modules/             # Feature modules
│   ├── auth/           # Authentication
│   ├── parking/        # Parking management
│   ├── routing/        # Route calculation
│   └── users/          # User management
├── common/             # Shared code
│   ├── decorators/     # Custom decorators
│   ├── filters/        # Exception filters
│   ├── guards/         # Auth guards
│   └── pipes/          # Validation pipes
├── config/             # Configuration
│   ├── database.config.ts
│   ├── redis.config.ts
│   └── app.config.ts
└── database/           # Database files
    ├── migrations/     # Database migrations
    ├── seeds/          # Seed data
    └── entities/       # TypeORM entities

🔄 Development Workflow

Git Workflow

# 1. Create feature branch
git checkout -b feature/parking-search-improvements

# 2. Make changes with atomic commits
git add .
git commit -m "feat(parking): add distance-based search filtering"

# 3. Push and create PR
git push origin feature/parking-search-improvements

Commit Message Convention

type(scope): description

Types:
- feat: New feature
- fix: Bug fix
- docs: Documentation changes
- style: Code style changes
- refactor: Code refactoring
- test: Adding tests
- chore: Maintenance tasks

Examples:
feat(map): add real-time parking availability indicators
fix(routing): resolve incorrect distance calculations
docs(api): update parking endpoint documentation

Development Scripts

{
  "scripts": {
    "dev": "next dev",
    "dev:backend": "cd backend && npm run start:dev",
    "dev:all": "concurrently \"npm run dev\" \"npm run dev:backend\"",
    "build": "next build",
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage",
    "lint": "eslint . --ext .ts,.tsx",
    "lint:fix": "eslint . --ext .ts,.tsx --fix",
    "type-check": "tsc --noEmit"
  }
}

📝 Coding Standards

TypeScript Configuration

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["dom", "dom.iterable", "es6"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "plugins": [
      {
        "name": "next"
      }
    ],
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"],
      "@/components/*": ["./src/components/*"],
      "@/services/*": ["./src/services/*"],
      "@/types/*": ["./src/types/*"]
    }
  }
}

ESLint Configuration

// .eslintrc.json
{
  "extends": [
    "next/core-web-vitals",
    "@typescript-eslint/recommended",
    "prettier"
  ],
  "rules": {
    "@typescript-eslint/no-unused-vars": "error",
    "@typescript-eslint/explicit-function-return-type": "warn",
    "prefer-const": "error",
    "no-var": "error",
    "react-hooks/exhaustive-deps": "warn"
  }
}

Code Style Guidelines

Frontend Components

// ✅ Good: Functional component with proper typing
interface ParkingListProps {
  parkingLots: ParkingLot[];
  onSelect: (lot: ParkingLot) => void;
  loading?: boolean;
}

export const ParkingList: React.FC<ParkingListProps> = ({
  parkingLots,
  onSelect,
  loading = false
}) => {
  const [selectedId, setSelectedId] = useState<string | null>(null);

  const handleSelect = useCallback((lot: ParkingLot) => {
    setSelectedId(lot.id);
    onSelect(lot);
  }, [onSelect]);

  if (loading) {
    return <LoadingSpinner />;
  }

  return (
    <div className="parking-list">
      {parkingLots.map((lot) => (
        <ParkingCard
          key={lot.id}
          lot={lot}
          isSelected={selectedId === lot.id}
          onClick={() => handleSelect(lot)}
        />
      ))}
    </div>
  );
};

Backend Services

// ✅ Good: Service with proper error handling and typing
@Injectable()
export class ParkingService {
  constructor(
    @InjectRepository(ParkingLot)
    private readonly parkingRepository: Repository<ParkingLot>,
    private readonly cacheService: CacheService,
    private readonly logger: Logger
  ) {}

  async findNearbyParking(
    dto: FindNearbyParkingDto
  ): Promise<ParkingLot[]> {
    try {
      const cacheKey = `nearby:${dto.latitude}:${dto.longitude}:${dto.radius}`;
      
      // Check cache first
      const cached = await this.cacheService.get<ParkingLot[]>(cacheKey);
      if (cached) {
        return cached;
      }

      // Query database with spatial index
      const lots = await this.parkingRepository
        .createQueryBuilder('lot')
        .where(
          'ST_DWithin(lot.location::geography, ST_Point(:lng, :lat)::geography, :radius)',
          {
            lng: dto.longitude,
            lat: dto.latitude,
            radius: dto.radius
          }
        )
        .andWhere('lot.isActive = :isActive', { isActive: true })
        .orderBy(
          'ST_Distance(lot.location::geography, ST_Point(:lng, :lat)::geography)',
          'ASC'
        )
        .limit(dto.limit || 20)
        .getMany();

      // Cache results
      await this.cacheService.set(cacheKey, lots, 300); // 5 minutes

      return lots;
    } catch (error) {
      this.logger.error('Failed to find nearby parking', error);
      throw new InternalServerErrorException('Failed to find nearby parking');
    }
  }
}

🧪 Testing Strategy

Test Structure

tests/
├── unit/              # Unit tests
├── integration/       # Integration tests
├── e2e/              # End-to-end tests
└── fixtures/         # Test data

Frontend Testing

// components/ParkingList.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { ParkingList } from './ParkingList';
import { mockParkingLots } from '../../../tests/fixtures/parking';

describe('ParkingList', () => {
  const mockOnSelect = jest.fn();

  beforeEach(() => {
    mockOnSelect.mockClear();
  });

  it('renders parking lots correctly', () => {
    render(
      <ParkingList 
        parkingLots={mockParkingLots} 
        onSelect={mockOnSelect} 
      />
    );

    expect(screen.getByText('Central Mall Parking')).toBeInTheDocument();
    expect(screen.getByText('$5/hour')).toBeInTheDocument();
  });

  it('calls onSelect when parking lot is clicked', () => {
    render(
      <ParkingList 
        parkingLots={mockParkingLots} 
        onSelect={mockOnSelect} 
      />
    );

    fireEvent.click(screen.getByText('Central Mall Parking'));
    expect(mockOnSelect).toHaveBeenCalledWith(mockParkingLots[0]);
  });

  it('shows loading spinner when loading', () => {
    render(
      <ParkingList 
        parkingLots={[]} 
        onSelect={mockOnSelect} 
        loading={true}
      />
    );

    expect(screen.getByRole('status')).toBeInTheDocument();
  });
});

Backend Testing

// parking/parking.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { ParkingService } from './parking.service';
import { ParkingLot } from './entities/parking-lot.entity';
import { mockRepository } from '../../tests/mocks/repository.mock';

describe('ParkingService', () => {
  let service: ParkingService;
  let repository: any;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        ParkingService,
        {
          provide: getRepositoryToken(ParkingLot),
          useValue: mockRepository,
        },
      ],
    }).compile();

    service = module.get<ParkingService>(ParkingService);
    repository = module.get(getRepositoryToken(ParkingLot));
  });

  describe('findNearbyParking', () => {
    it('should return nearby parking lots', async () => {
      const mockLots = [/* mock data */];
      repository.createQueryBuilder().getMany.mockResolvedValue(mockLots);

      const result = await service.findNearbyParking({
        latitude: 1.3521,
        longitude: 103.8198,
        radius: 1000,
      });

      expect(result).toEqual(mockLots);
      expect(repository.createQueryBuilder).toHaveBeenCalled();
    });
  });
});

Integration Testing

// parking/parking.controller.integration.spec.ts
import { Test } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from '../app.module';

describe('ParkingController (Integration)', () => {
  let app: INestApplication;

  beforeAll(async () => {
    const moduleRef = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();

    app = moduleRef.createNestApplication();
    await app.init();
  });

  it('/parking/nearby (POST)', () => {
    return request(app.getHttpServer())
      .post('/parking/nearby')
      .send({
        latitude: 1.3521,
        longitude: 103.8198,
        radius: 1000,
      })
      .expect(200)
      .expect((res) => {
        expect(res.body).toHaveProperty('data');
        expect(Array.isArray(res.body.data)).toBe(true);
      });
  });

  afterAll(async () => {
    await app.close();
  });
});

🐛 Debugging

Frontend Debugging

// Development debugging utilities
export const debugLog = (message: string, data?: any): void => {
  if (process.env.NODE_ENV === 'development') {
    console.log(`[DEBUG] ${message}`, data);
  }
};

// React Developer Tools
// Component debugging
export const ParkingDebugger: React.FC = () => {
  const [parkingLots, setParkingLots] = useLocalStorage('debug:parking', []);
  
  useEffect(() => {
    // Log component updates
    debugLog('ParkingDebugger mounted');
    
    return () => {
      debugLog('ParkingDebugger unmounted');
    };
  }, []);

  return (
    <div className="debug-panel">
      <h3>Parking Debug Info</h3>
      <pre>{JSON.stringify(parkingLots, null, 2)}</pre>
    </div>
  );
};

Backend Debugging

// Logger configuration
import { Logger } from '@nestjs/common';

@Injectable()
export class DebugService {
  private readonly logger = new Logger(DebugService.name);

  logRequest(req: Request, res: Response, next: NextFunction): void {
    const { method, originalUrl, body, query } = req;
    
    this.logger.debug(`${method} ${originalUrl}`, {
      body,
      query,
      timestamp: new Date().toISOString(),
    });
    
    next();
  }

  logDatabaseQuery(query: string, parameters?: any[]): void {
    this.logger.debug('Database Query', {
      query,
      parameters,
      timestamp: new Date().toISOString(),
    });
  }
}

VS Code Debug Configuration

// .vscode/launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Debug Frontend",
      "type": "node",
      "request": "launch",
      "cwd": "${workspaceFolder}/frontend",
      "runtimeExecutable": "npm",
      "runtimeArgs": ["run", "dev"]
    },
    {
      "name": "Debug Backend",
      "type": "node",
      "request": "launch",
      "cwd": "${workspaceFolder}/backend",
      "program": "${workspaceFolder}/backend/dist/main.js",
      "env": {
        "NODE_ENV": "development"
      },
      "console": "integratedTerminal",
      "restart": true,
      "protocol": "inspector"
    }
  ]
}

Performance Guidelines

Frontend Performance

// Use React.memo for expensive components
export const ParkingMap = React.memo<ParkingMapProps>(({ 
  parkingLots, 
  onMarkerClick 
}) => {
  // Component implementation
});

// Optimize re-renders with useMemo
const filteredLots = useMemo(() => {
  return parkingLots.filter(lot => 
    lot.availableSpaces > 0 && 
    lot.distance <= maxDistance
  );
}, [parkingLots, maxDistance]);

// Virtual scrolling for large lists
import { FixedSizeList as List } from 'react-window';

const VirtualizedParkingList: React.FC = ({ items }) => (
  <List
    height={600}
    itemCount={items.length}
    itemSize={120}
    itemData={items}
  >
    {ParkingRow}
  </List>
);

Backend Performance

// Database query optimization
@Injectable()
export class OptimizedParkingService {
  // Use spatial indexes
  async findNearbyOptimized(dto: FindNearbyParkingDto): Promise<ParkingLot[]> {
    return this.parkingRepository.query(`
      SELECT *
      FROM parking_lots
      WHERE ST_DWithin(
        location::geography,
        ST_Point($1, $2)::geography,
        $3
      )
      AND available_spaces > 0
      ORDER BY location <-> ST_Point($1, $2)
      LIMIT $4
    `, [dto.longitude, dto.latitude, dto.radius, dto.limit]);
  }

  // Implement caching
  @Cacheable('parking:nearby', 300) // 5 minutes
  async findNearbyCached(dto: FindNearbyParkingDto): Promise<ParkingLot[]> {
    return this.findNearbyOptimized(dto);
  }
}

// Connection pooling
export const databaseConfig: TypeOrmModuleOptions = {
  type: 'postgres',
  url: process.env.DATABASE_URL,
  extra: {
    max: 20, // maximum number of connections
    connectionTimeoutMillis: 2000,
    idleTimeoutMillis: 30000,
  },
};

🤝 Contributing

Pull Request Process

  1. Fork and Clone

    git clone https://github.com/your-username/smart-parking-finder.git
    cd smart-parking-finder
    git remote add upstream https://github.com/original/smart-parking-finder.git
    
  2. Create Feature Branch

    git checkout -b feature/your-feature-name
    
  3. Development

    • Follow coding standards
    • Write tests for new features
    • Update documentation
  4. Submit PR

    • Ensure all tests pass
    • Update CHANGELOG.md
    • Provide clear description

Code Review Guidelines

  • Code Quality: Follows TypeScript best practices
  • Testing: Adequate test coverage (>80%)
  • Performance: No performance regressions
  • Documentation: Updated documentation
  • Security: No security vulnerabilities

Issue Templates

## Bug Report

**Describe the bug**
A clear description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

**Expected behavior**
What you expected to happen.

**Screenshots**
If applicable, add screenshots.

**Environment:**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]

For more information, see the README and Technical Specification.