- Looking for a starting point or more instructions? Head over to{" "}
-
- Templates
- {" "}
- or the{" "}
-
- Learning
- {" "}
- center.
+
+
+
+
+ Hoş Geldiniz, Kenan
+
+
+ İşte bugünkü asansör servis ve üretim özeti.
-
+
+
+ {stats.map((stat, i) => (
+
+ ))}
+
+
+
+ {/* Son Aktiviteler */}
+
+
+
Son Aktiviteler
+
+
+
+ {recentActivity.map((act, i) => (
+
+
+
+
{act.title}
+
{act.location}
+
+
{act.date}
+
+ ))}
+
+
+
+ {/* Bekleyen Görevler / Notlar */}
+
+
Hatırlatıcılar
+
+
+
⚠️ Güneş Apt. için parça bekleniyor.
+
+
+
📅 Yarınki bakım rotası hazırlandı.
+
+
+
+
);
}
diff --git a/src/app/uretim/page.tsx b/src/app/uretim/page.tsx
new file mode 100644
index 0000000..9ca3a8c
--- /dev/null
+++ b/src/app/uretim/page.tsx
@@ -0,0 +1,214 @@
+'use client';
+
+import React, { useState } from 'react';
+import {
+ Plus,
+ Search,
+ Calendar,
+ CheckCircle2,
+ Circle,
+ ChevronRight,
+ MoreVertical,
+ Factory
+} from 'lucide-react';
+
+export default function UretimPlani() {
+ const [activeTab, setActiveTab] = useState('active');
+
+ const productionPlans = [
+ {
+ id: 1,
+ proje: 'Koru Sitesi - Asansör 1',
+ bina: 'Batıkent, Ankara',
+ asama: 'Malzeme Temini',
+ yuzde: 35,
+ tarih: '15 Mayıs',
+ steps: ['Proje', 'Malzeme', 'Karkas', 'Ray', 'Motor', 'Kabin', 'Test']
+ },
+ {
+ id: 2,
+ proje: 'Mavi Plaza - Yük Asansörü',
+ bina: 'Çankaya, Ankara',
+ asama: 'Ray Montajı',
+ yuzde: 60,
+ tarih: '28 Nisan',
+ steps: ['Proje', 'Malzeme', 'Karkas', 'Ray', 'Motor', 'Kabin', 'Test']
+ },
+ ];
+
+ return (
+
+
+
+
Üretim Planlama
+
Yeni projelerin imalat ve montaj süreçlerini yönetin.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {productionPlans.map((plan) => (
+
+
+
+
{plan.proje}
+
{plan.bina}
+
+
+
+
Hedef Bitiş
+
{plan.tarih}
+
+
+
+
+
+ {/* Progress Aşamaları */}
+
+
+
+
+ {plan.steps.map((step, idx) => {
+ const isCompleted = idx < plan.steps.indexOf(plan.asama.split(' ')[0]) || plan.yuzde === 100;
+ const isCurrent = plan.asama.startsWith(step);
+
+ return (
+
+
+ {isCompleted ? : }
+
+
+ {step}
+
+
+ );
+ })}
+
+
+
+
+
+ Son güncelleme: Dün
+
+
+
+
+ ))}
+
+
+ );
+}
diff --git a/src/components/BuildingCard.tsx b/src/components/BuildingCard.tsx
new file mode 100644
index 0000000..d9df1f5
--- /dev/null
+++ b/src/components/BuildingCard.tsx
@@ -0,0 +1,107 @@
+'use client';
+
+import React from 'react';
+import { Building2, MapPin, Elevator, CreditCard, ChevronRight } from 'lucide-react';
+import { motion } from 'framer-motion';
+
+interface BuildingCardProps {
+ name: string;
+ address: string;
+ elevators: number;
+ balance: number;
+ status: 'active' | 'passive';
+}
+
+const BuildingCard = ({ name, address, elevators, balance, status }: BuildingCardProps) => {
+ return (
+
+
+
+
+
+
+ {status === 'active' ? 'Aktif' : 'Pasif'}
+
+
+
+ {name}
+
+
+ {address}
+
+
+
+
+
+
+ Asansör
+
+
{elevators} Adet
+
+
+
+
+ Bakiye
+
+
0 ? '#f43f5e' : '#10b981'
+ }}>
+ {balance.toLocaleString('tr-TR')} ₺
+
+
+
+
+
+
+
+
+ );
+};
+
+export default BuildingCard;
+
+// Helper to use Wrench icon in the snippet
+import { Wrench } from 'lucide-react';
diff --git a/src/components/Header.tsx b/src/components/Header.tsx
new file mode 100644
index 0000000..a6fc5ce
--- /dev/null
+++ b/src/components/Header.tsx
@@ -0,0 +1,62 @@
+'use client';
+
+import React from 'react';
+import { Bell, Search, Menu } from 'lucide-react';
+
+const Header = () => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Header;
diff --git a/src/components/MobileNav.tsx b/src/components/MobileNav.tsx
new file mode 100644
index 0000000..2e8d320
--- /dev/null
+++ b/src/components/MobileNav.tsx
@@ -0,0 +1,79 @@
+'use client';
+
+import React from 'react';
+import Link from 'next/link';
+import { usePathname } from 'next/navigation';
+import {
+ LayoutDashboard,
+ Building2,
+ Wrench,
+ AlertTriangle,
+ PlusSquare
+} from 'lucide-react';
+
+const MobileNav = () => {
+ const pathname = usePathname();
+
+ const items = [
+ { icon: LayoutDashboard, href: '/', label: 'Ana Sayfa' },
+ { icon: Building2, href: '/binalar', label: 'Binalar' },
+ { icon: PlusSquare, href: '/bakimlar/yeni', label: 'Yeni Bakım', primary: true },
+ { icon: AlertTriangle, href: '/arizalar', label: 'Arızalar' },
+ { icon: Wrench, href: '/uretim', label: 'Üretim' },
+ ];
+
+ return (
+
+ );
+};
+
+export default MobileNav;
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx
new file mode 100644
index 0000000..5deff94
--- /dev/null
+++ b/src/components/Sidebar.tsx
@@ -0,0 +1,71 @@
+'use client';
+
+import React from 'react';
+import Link from 'next/link';
+import { usePathname } from 'next/navigation';
+import {
+ LayoutDashboard,
+ Building2,
+ Wrench,
+ AlertTriangle,
+ History,
+ Settings,
+ UserCircle,
+ Factory
+} from 'lucide-react';
+
+const Sidebar = () => {
+ const pathname = usePathname();
+
+ const navItems = [
+ { name: 'Gözlem Paneli', href: '/', icon: LayoutDashboard },
+ { name: 'Binalar & Asansörler', href: '/binalar', icon: Building2 },
+ { name: 'Bakım Kayıtları', href: '/bakimlar', icon: History },
+ { name: 'Arıza Talepleri', href: '/arizalar', icon: AlertTriangle },
+ { name: 'Üretim Planlama', href: '/uretim', icon: Factory },
+ { name: 'Teknisyenler', href: '/teknisyenler', icon: Wrench },
+ { name: 'Ayarlar', href: '/ayarlar', icon: Settings },
+ ];
+
+ return (
+
+ );
+};
+
+export default Sidebar;
diff --git a/src/components/StatsCard.tsx b/src/components/StatsCard.tsx
new file mode 100644
index 0000000..4048d45
--- /dev/null
+++ b/src/components/StatsCard.tsx
@@ -0,0 +1,53 @@
+'use client';
+
+import React from 'react';
+import { LucideIcon } from 'lucide-react';
+import { motion } from 'framer-motion';
+
+interface StatsCardProps {
+ title: string;
+ value: string | number;
+ icon: LucideIcon;
+ trend?: string;
+ color: string;
+}
+
+const StatsCard = ({ title, value, icon: Icon, trend, color }: StatsCardProps) => {
+ return (
+
+
+
+
+
+ {trend && (
+
+ {trend}
+
+ )}
+
+
+
+ );
+};
+
+export default StatsCard;
diff --git a/src/lib/db.ts b/src/lib/db.ts
new file mode 100644
index 0000000..ddd1811
--- /dev/null
+++ b/src/lib/db.ts
@@ -0,0 +1,56 @@
+import { CapacitorSQLite, SQLiteConnection, SQLiteDBConnection } from '@capacitor-community/sqlite';
+import { supabase } from './supabase';
+
+class LocalDB {
+ private sqlite: SQLiteConnection | null = null;
+ private db: SQLiteDBConnection | null = null;
+
+ async init() {
+ if (this.db) return;
+
+ this.sqlite = new SQLiteConnection(CapacitorSQLite);
+ this.db = await this.sqlite.createConnection('asansor_db', false, 'no-encryption', 1, false);
+ await this.db.open();
+
+ // Tabloları oluştur
+ const schema = `
+ CREATE TABLE IF NOT EXISTS bakimlar (
+ id TEXT PRIMARY KEY,
+ asansor_id TEXT,
+ tarih TEXT,
+ notlar TEXT,
+ is_synced INTEGER DEFAULT 0
+ );
+ `;
+ await this.db.execute(schema);
+ }
+
+ async saveBakim(bakim: any) {
+ await this.init();
+ const query = `INSERT INTO bakimlar (id, asansor_id, tarih, notlar, is_synced) VALUES (?, ?, ?, ?, 0)`;
+ await this.db?.run(query, [bakim.id, bakim.asansor_id, bakim.tarih, bakim.notlar]);
+ }
+
+ async sync() {
+ await this.init();
+ // Senkronize edilmemiş kayıtları getir
+ const result = await this.db?.query('SELECT * FROM bakimlar WHERE is_synced = 0');
+
+ if (result && result.values && result.values.length > 0) {
+ for (const row of result.values) {
+ const { error } = await supabase.from('bakim_kayitlari').insert({
+ id: row.id,
+ asansor_id: row.asansor_id,
+ tarih: row.tarih,
+ notlar: row.notlar
+ });
+
+ if (!error) {
+ await this.db?.run('UPDATE bakimlar SET is_synced = 1 WHERE id = ?', [row.id]);
+ }
+ }
+ }
+ }
+}
+
+export const localDB = new LocalDB();
diff --git a/src/lib/sms.ts b/src/lib/sms.ts
new file mode 100644
index 0000000..50ce2b5
--- /dev/null
+++ b/src/lib/sms.ts
@@ -0,0 +1,37 @@
+interface SMSResponse {
+ success: boolean;
+ message: string;
+ data?: any;
+}
+
+/**
+ * NETGSM SMS Gönderim Servisi
+ * Not: Bu servis sunucu tarafında (Server Side) çağrılmalıdır veya API rotası üzerinden yönlendirilmelidir.
+ */
+export async function sendSMS(target: string, message: string): Promise
{
+ const user = process.env.NETGSM_USER;
+ const pass = process.env.NETGSM_PASS;
+ const title = process.env.NETGSM_TITLE;
+
+ if (!user || !pass) {
+ console.error('NETGSM credentials are missing');
+ return { success: false, message: 'SMS servis ayarları eksik.' };
+ }
+
+ try {
+ const url = `https://api.netgsm.com.tr/sms/send/get/?usercode=${user}&password=${pass}&gsm=${target}&message=${encodeURIComponent(message)}&msgheader=${title}`;
+
+ const response = await fetch(url);
+ const result = await response.text();
+
+ // NETGSM genellikle "00" ile başlayan bir kod dönerse başarılı sayılır
+ if (result.startsWith('00')) {
+ return { success: true, message: 'SMS başarıyla gönderildi.', data: result };
+ } else {
+ return { success: false, message: `SMS gönderilemedi. Hata kodu: ${result}` };
+ }
+ } catch (error) {
+ console.error('SMS sending error:', error);
+ return { success: false, message: 'Sunucu hatası oluştu.' };
+ }
+}
diff --git a/src/lib/supabase.ts b/src/lib/supabase.ts
new file mode 100644
index 0000000..d7b7b90
--- /dev/null
+++ b/src/lib/supabase.ts
@@ -0,0 +1,6 @@
+import { createClient } from '@supabase/supabase-js';
+
+const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || '';
+const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || '';
+
+export const supabase = createClient(supabaseUrl, supabaseAnonKey);