731 lines
15 KiB
Markdown
731 lines
15 KiB
Markdown
# 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. |