Files
ytp/docs/FRONTEND.md
2025-11-03 23:12:45 +03:00

15 KiB
Raw Permalink Blame History

Frontend Mimarisi Dokümantasyonu

Yakıt Takip Sistemi frontend mimarisi, Svelte bileşenleri, state management ve kullanıcı etkileşimi hakkında detaylı bilgi.

📋 İçerlik

🏗️ Mimari Genel Bakış

Teknoloji Stack

  • Framework: Svelte 4.x
  • Build Tool: Vite 5.x
  • Bundler: Rollup (via Vite)
  • CSS: Pure CSS3 (no framework)
  • Icons: Font Awesome 6.x
  • Real-time: Socket.IO Client
  • HTTP Client: Fetch API (custom wrapper)

Tasarım Prensipleri

  • Component-based: Tek sorumluluk ilkesi
  • Reactive: Svelte reaktif değişkenleri
  • Event-driven: Custom events ile haberleşme
  • Mobile-first: Responsive tasarım
  • Accessibility: WCAG 2.1 AA uyumluluğu

Proje Yapısı

client/src/
├── components/          # Svelte bileşenleri
│   ├── LoginView.svelte           # Giriş ekranı
│   ├── AdminPanel.svelte          # Admin yönetim paneli
│   ├── FuelManagerPanel.svelte    # Yakıt sorumlusu paneli
│   ├── InventoryManagerPanel.svelte # Mal sorumlusu paneli
│   └── RoleWelcome.svelte         # Rol karşılama ekranı
├── lib/                # Yardımcı kütüphaneler
│   └── numberToWordsTr.js        # Sayı→metin çeviri
├── api.js              # API istemcisi
├── app.css             # Global stiller
├── main.js             # Uygulama başlangıcı
└── App.svelte          # Ana bileşen

🧩 Component Hiyerarşisi

App.svelte (Root Component)

Amaç: Uygulamanın ana bileşeni ve routing mantığı

Sorumluluklar

  • Oturum yönetimi (localStorage + state)
  • Component routing (role-based)
  • Global durum yönetimi
  • Layout ve tema yönetimi
  • Error boundaries

Key Features

// State management
let user = null;
let token = null;
let loading = false;
let feedback = '';

// Session persistence
onMount(async () => {
  const storedToken = localStorage.getItem('sessionToken');
  if (storedToken) {
    token = storedToken;
    // Validate session
  }
});

// Event handling
function handleLoginSuccess(event) {
  user = event.detail.user;
  token = event.detail.token;
  localStorage.setItem('sessionToken', token);
}

Template Structure

<div class="app-shell">
  <header class="hero">
    <!-- Hero content with user info -->
  </header>
  <main class="content">
    {#if user}
      <!-- Role-based component rendering -->
      {#if user.role === 'admin'}
        <AdminPanel {token} />
      {:else if user.role === 'fuel_manager'}
        <FuelManagerPanel {user} {token} />
      {:else if user.role === 'inventory_manager'}
        <InventoryManagerPanel {user} {token} />
      {:else}
        <RoleWelcome {user} />
      {/if}
    {:else}
      <LoginView on:success={handleLoginSuccess} />
    {/if}
  </main>
</div>

LoginView.svelte

Amaç: Kullanıcı kimlik doğrulama arayüzü

Features

  • Form validasyonu
  • Hata yönetimi
  • Loading states
  • Remember me (localStorage)
  • Custom event dispatch

State Management

let username = '';
let password = '';
let loading = false;
let error = '';

// Form validasyonu
function validateForm() {
  if (!username.trim() || !password.trim()) {
    error = 'Kullanıcı adı ve şifre zorunludur.';
    return false;
  }
  return true;
}

// API çağrısı
async function handleLogin() {
  if (!validateForm()) return;

  loading = true;
  error = '';

  try {
    const response = await login(username, password);
    dispatch('success', {
      user: response.user,
      token: response.token
    });
  } catch (err) {
    error = err.message || 'Giriş başarısız.';
  } finally {
    loading = false;
  }
}

AdminPanel.svelte

Amaç: Admin yönetim paneli

Modules

  • Kullanıcı yönetimi (Mal sorumluları)
  • Araç yönetimi
  • Birlik yönetimi
  • Personel yönetimi

Component Pattern

// Tab module state
let activeTab = 'managers';
let managers = [];
let vehicles = [];
let units = [];
let personnel = [];

let showCreateModal = false;
let editingItem = null;
let loading = false;

// Data fetching
async function loadManagers() {
  loading = true;
  try {
    const response = await listInventoryManagers(token);
    managers = response.managers;
  } catch (err) {
    console.error('Managers load error:', err);
  } finally {
    loading = false;
  }
}

// CRUD operations
async function handleCreateManager(data) {
  try {
    await createInventoryManager(data, token);
    await loadManagers();
    showCreateModal = false;
  } catch (err) {
    error = err.message;
  }
}

FuelManagerPanel.svelte

Amaç: Yakıt sorumlusu işlemleri paneli

Key Features

  • Kaynak yönetimi (araç, birlik, personel)
  • Yakıt fişi oluşturma
  • Fiş takibi ve yönetimi
  • PDF indirme
  • Real-time bildirimler

Socket.IO Integration

import { io } from 'socket.io-client';

let socket = null;

onMount(() => {
  socket = io('http://localhost:5005', {
    auth: { token }
  });

  socket.on('fuelSlip:status', (slip) => {
    // Update UI with new status
    const index = slips.findIndex(s => s.id === slip.id);
    if (index !== -1) {
      slips[index] = slip;
    }
  });

  return () => {
    if (socket) socket.disconnect();
  };
});

InventoryManagerPanel.svelte

Amaç: Mal sorumlusu onay paneli

Features

  • Atanan fiş listesi
  • Onay/reddetme işlemleri
  • Real-time bildirimler
  • Filtreleme ve arama

Status Management

let assignedSlips = [];
let filter = 'all'; // all, pending, approved, rejected

function getFilteredSlips() {
  if (filter === 'all') return assignedSlips;
  return assignedSlips.filter(slip => slip.status === filter);
}

async function handleStatusUpdate(slipId, status, reason = null) {
  try {
    const response = await updateFuelSlipStatus(slipId, status, reason, token);

    // Update local state
    const index = assignedSlips.findIndex(s => s.id === slipId);
    if (index !== -1) {
      assignedSlips[index] = response.slip;
    }
  } catch (err) {
    error = err.message;
  }
}

📊 State Management

Global State (App.svelte)

// User session state
let user = null;
let token = null;
let loading = false;

// Session persistence
function saveSession(userData, sessionToken) {
  user = userData;
  token = sessionToken;
  localStorage.setItem('sessionToken', sessionToken);
}

function clearSession() {
  user = null;
  token = null;
  localStorage.removeItem('sessionToken');
}

Component State Patterns

Form State

// Generic form state pattern
let formData = {
  username: '',
  password: '',
  remember: false
};

let errors = {};
let touched = {};
let isSubmitting = false;

function validateField(field) {
  touched[field] = true;
  // Validation logic
}

function isValidForm() {
  return Object.keys(errors).every(key => !errors[key]);
}

List State

// Generic list state pattern
let items = [];
let loading = false;
let error = null;
let selectedItems = [];

async function loadItems() {
  loading = true;
  error = null;
  try {
    const response = await fetchItems(token);
    items = response.data;
  } catch (err) {
    error = err.message;
  } finally {
    loading = false;
  }
}

function handleItemSelect(itemId) {
  const index = selectedItems.indexOf(itemId);
  if (index > -1) {
    selectedItems.splice(index, 1);
  } else {
    selectedItems.push(itemId);
  }
}

Reactive State with Svelte

// Reactive statements
$: filteredItems = items.filter(item =>
  item.name.toLowerCase().includes(searchTerm.toLowerCase())
);

$: totalAmount = items.reduce((sum, item) => sum + item.amount, 0);

$: isValid = formData.username && formData.password && !Object.values(errors).some(Boolean);

🛣️ Routing

Role-based Routing

// App.svelte routing logic
{#if user.role === 'admin'}
  <AdminPanel {token} />
{:else if user.role === 'fuel_manager'}
  <FuelManagerPanel {user} {token} />
{:else if user.role === 'inventory_manager'}
  <InventoryManagerPanel {user} {token} />
{:else}
  <RoleWelcome {user} />
{/if}

Tab-based Navigation

// Tab management pattern
let activeTab = 'overview';

const tabs = [
  { id: 'overview', label: 'Genel Bakış', icon: 'dashboard' },
  { id: 'managers', label: 'Mal Sorumluları', icon: 'users' },
  { id: 'vehicles', label: 'Araçlar', icon: 'car' },
  { id: 'units', label: 'Birlikler', icon: 'building' },
  { id: 'personnel', label: 'Personel', icon: 'user-tie' }
];

function switchTab(tabId) {
  activeTab = tabId;
  loadTabData(tabId);
}

🌐 API Entegrasyonu

API Client (api.js)

// Base configuration
const BASE_URL = 'http://localhost:5005/api';

class ApiClient {
  constructor() {
    this.token = null;
  }

  setToken(token) {
    this.token = token;
  }

  async request(endpoint, options = {}) {
    const url = `${BASE_URL}${endpoint}`;
    const config = {
      headers: {
        'Content-Type': 'application/json',
        ...options.headers
      },
      ...options
    };

    if (this.token) {
      config.headers['X-Session-Token'] = this.token;
    }

    const response = await fetch(url, config);

    if (!response.ok) {
      const error = await response.json();
      throw new Error(error.message || 'API request failed');
    }

    return response.json();
  }

  // Authentication methods
  async login(username, password) {
    return this.request('/auth/login', {
      method: 'POST',
      body: JSON.stringify({ username, password })
    });
  }

  async logout() {
    return this.request('/auth/logout', { method: 'POST' });
  }

  // CRUD methods
  async get(endpoint) {
    return this.request(endpoint);
  }

  async post(endpoint, data) {
    return this.request(endpoint, {
      method: 'POST',
      body: JSON.stringify(data)
    });
  }

  async put(endpoint, data) {
    return this.request(endpoint, {
      method: 'PUT',
      body: JSON.stringify(data)
    });
  }

  async delete(endpoint) {
    return this.request(endpoint, { method: 'DELETE' });
  }
}

// Singleton instance
const api = new ApiClient();
export default api;

Usage in Components

import api from './api.js';

// Component usage
let token = '';

async function loadData() {
  try {
    const data = await api.get('/vehicles');
    vehicles = data.vehicles;
  } catch (error) {
    console.error('Load error:', error);
    errorMessage = error.message;
  }
}

async function handleSubmit() {
  try {
    isSubmitting = true;
    const result = await api.post('/vehicles', formData);
    await loadData();
    showSuccess('Araç başarıyla eklendi');
  } catch (error) {
    showError(error.message);
  } finally {
    isSubmitting = false;
  }
}

🎨 Styling ve Tasarım

CSS Architecture

/* Global variables (app.css) */
:root {
  /* Colors */
  --primary-color: #4c6ef5;
  --secondary-color: #6c757d;
  --success-color: #51cf66;
  --warning-color: #ffd43b;
  --danger-color: #ff6b6b;

  /* Spacing */
  --spacing-xs: 0.25rem;
  --spacing-sm: 0.5rem;
  --spacing-md: 1rem;
  --spacing-lg: 1.5rem;
  --spacing-xl: 2rem;

  /* Typography */
  --font-size-xs: 0.75rem;
  --font-size-sm: 0.875rem;
  --font-size-base: 1rem;
  --font-size-lg: 1.125rem;
  --font-size-xl: 1.25rem;

  /* Shadows */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
  --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.07);
  --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
}

Component Styling Pattern

/* Component-specific styles */
.panel-container {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-lg);
  padding: var(--spacing-xl);
}

.tab-navigation {
  display: flex;
  border-bottom: 2px solid var(--border-color);
  margin-bottom: var(--spacing-lg);
}

.tab-button {
  padding: var(--spacing-sm) var(--spacing-lg);
  border: none;
  background: none;
  color: var(--text-muted);
  cursor: pointer;
  transition: all 0.2s ease;
  border-bottom: 2px solid transparent;
}

.tab-button:hover {
  color: var(--primary-color);
  background: var(--primary-light);
}

.tab-button.active {
  color: var(--primary-color);
  border-bottom-color: var(--primary-color);
}

Responsive Design

/* Mobile-first responsive design */
.content {
  padding: var(--spacing-md);
}

@media (min-width: 768px) {
  .content {
    padding: var(--spacing-lg);
  }
}

@media (min-width: 1024px) {
  .content {
    padding: var(--spacing-xl);
    max-width: 1280px;
    margin: 0 auto;
  }
}

.grid {
  display: grid;
  gap: var(--spacing-md);
}

@media (min-width: 768px) {
  .grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 1024px) {
  .grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

Performance Optimizasyonu

Code Splitting

// Dynamic imports for lazy loading
const AdminPanel = lazy(() => import('./components/AdminPanel.svelte'));
const FuelManagerPanel = lazy(() => import('./components/FuelManagerPanel.svelte'));

// Usage with suspense
{#if user.role === 'admin'}
  <Suspense fallback={<div>Loading...</div>}>
    <AdminPanel {token} />
  </Suspense>
{/if}

Component Optimization

// Reactivity optimization
$: expensiveValue = computeExpensiveValue(data);

// Memoization with Svelte
function memoize(fn) {
  let lastArgs;
  let lastResult;

  return (...args) => {
    if (args.length !== lastArgs?.length || args.some((arg, i) => arg !== lastArgs[i])) {
      lastArgs = args;
      lastResult = fn(...args);
    }
    return lastResult;
  };
}

const memoizedFilter = memoize((items, filter) =>
  items.filter(item => matchesFilter(item, filter))
);

Asset Optimization

// vite.config.js
export default {
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['svelte'],
          socketio: ['socket.io-client'],
          utils: ['./src/lib/numberToWordsTr.js']
        }
      }
    },
    assetsInlineLimit: 4096
  },
  optimizeDeps: {
    include: ['svelte', 'socket.io-client']
  }
};

Bundle Analysis

# Bundle analyzer
npm install --save-dev rollup-plugin-visualizer

# Build with analysis
npm run build -- --analyze

🔧 Development Tools

Svelte DevTools

// DevTools integration
if (import.meta.env.DEV) {
  import('svelte-devtools');
}

Hot Module Replacement

// Vite HMR configuration
export default {
  server: {
    hmr: {
      overlay: true
    }
  }
};

TypeScript Support (Optional)

// svelte.config.js
import { vitePreprocess } from '@sveltejs/kit/vite';

export default {
  preprocess: vitePreprocess(),
  compilerOptions: {
    dev: import.meta.env.DEV
  }
};

Not: Frontend mimarisi sürekli olarak geliştirilmektedir. Yeni özellikler eklerken bu dokümantasyonu güncel tutun.