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

731 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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ış](#mimari-genel-bakış)
- [Component Hiyerarşisi](#component-hiyerarşisi)
- [State Management](#state-management)
- [Routing](#routing)
- [API Entegrasyonu](#api-entegrasyonu)
- [Styling ve Tasarım](#styling-ve-tasarım)
- [Performance Optimizasyonu](#performance-optimizasyonu)
## 🏗️ 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
```javascript
// 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
```html
<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
```javascript
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
```javascript
// 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
```javascript
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
```javascript
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)
```javascript
// 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
```javascript
// 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
```javascript
// 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
```javascript
// 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
```javascript
// 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
```javascript
// 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)
```javascript
// 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
```javascript
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
```css
/* 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
```css
/* 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
```css
/* 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
```javascript
// 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
```javascript
// 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
```javascript
// 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
```bash
# Bundle analyzer
npm install --save-dev rollup-plugin-visualizer
# Build with analysis
npm run build -- --analyze
```
## 🔧 Development Tools
### Svelte DevTools
```javascript
// DevTools integration
if (import.meta.env.DEV) {
import('svelte-devtools');
}
```
### Hot Module Replacement
```javascript
// Vite HMR configuration
export default {
server: {
hmr: {
overlay: true
}
}
};
```
### TypeScript Support (Optional)
```javascript
// 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.