15 KiB
15 KiB
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ış
- Component Hiyerarşisi
- State Management
- Routing
- API Entegrasyonu
- Styling ve Tasarım
- 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
// 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.