Numex yayın: server.js
This commit is contained in:
parent
2249e1d556
commit
868f02de6a
146
server.js
Normal file
146
server.js
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
// server.js — Express backend
|
||||||
|
// Randevu sistemi: müşteri kaydı + çakışma kontrolü + günlük liste
|
||||||
|
|
||||||
|
const express = require('express');
|
||||||
|
const path = require('path');
|
||||||
|
const storage = require('./storage');
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const PORT = process.env.PORT || 3000;
|
||||||
|
|
||||||
|
// Çalışma saatleri (09:00 - 18:00, her saat başı slot)
|
||||||
|
const OPEN_HOUR = 9;
|
||||||
|
const CLOSE_HOUR = 18;
|
||||||
|
const SLOT_MINUTES = 60;
|
||||||
|
|
||||||
|
app.use(express.json());
|
||||||
|
app.use(express.static(path.join(__dirname, 'public')));
|
||||||
|
|
||||||
|
// --- Yardımcılar ---
|
||||||
|
|
||||||
|
// Tarihi "YYYY-MM-DD" biçimine getirir
|
||||||
|
function normalizeDate(date) {
|
||||||
|
return date; // beklenen format zaten "YYYY-MM-DD"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Saati "HH:MM" dakikaya çevirir (çakışma için)
|
||||||
|
function toMinutes(hhmm) {
|
||||||
|
const [h, m] = hhmm.split(':').map(Number);
|
||||||
|
return h * 60 + m;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Belirtilen günde belirtilen zaman aralığına çakışan randevu var mı?
|
||||||
|
function isConflict(list, date, startMin, durationMin) {
|
||||||
|
const end = startMin + durationMin;
|
||||||
|
return list.some(a => {
|
||||||
|
if (a.date !== date) return false;
|
||||||
|
const aStart = toMinutes(a.time);
|
||||||
|
const aEnd = aStart + (a.duration || 0);
|
||||||
|
// Çakışma: iki aralık birbirine giriyorsa
|
||||||
|
return startMin < aEnd && end > aStart;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Beklenen çalışma saati içinde mi?
|
||||||
|
function withinHours(startMin) {
|
||||||
|
return startMin >= OPEN_HOUR * 60 && (startMin + SLOT_MINUTES) <= CLOSE_HOUR * 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- API ---
|
||||||
|
|
||||||
|
// Sağlık kontrolü
|
||||||
|
app.get('/api/health', (req, res) => {
|
||||||
|
res.json({ ok: true, service: 'randevu-sistemi' });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Boş slotları döndürür (bugünün veya istenen günün)
|
||||||
|
app.get('/api/slots', (req, res) => {
|
||||||
|
const date = req.query.date || null;
|
||||||
|
const list = storage.getAll();
|
||||||
|
|
||||||
|
const slots = [];
|
||||||
|
for (let m = OPEN_HOUR * 60; m < CLOSE_HOUR * 60; m += SLOT_MINUTES) {
|
||||||
|
const hh = String(Math.floor(m / 60)).padStart(2, '0');
|
||||||
|
const mm = String(m % 60).padStart(2, '0');
|
||||||
|
const time = `${hh}:${mm}`;
|
||||||
|
// Belirli gün verildiyse o günkü çakışmaları, yoksa genel geçmiş/tekrarı kontrol etme
|
||||||
|
const busy = date ? isConflict(list, date, m, SLOT_MINUTES) : false;
|
||||||
|
if (!busy) {
|
||||||
|
slots.push(time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// date verilmediyse tüm saat dilimlerini döndür
|
||||||
|
if (!date) return res.json(slots);
|
||||||
|
res.json(slots);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Yeni randevu oluştur (çakışma kontrolü ile)
|
||||||
|
app.post('/api/appointments', (req, res) => {
|
||||||
|
const { name, phone, date, time, service, notes } = req.body;
|
||||||
|
|
||||||
|
// Doğrulama
|
||||||
|
if (!name || !phone || !date || !time) {
|
||||||
|
return res.status(400).json({ error: 'Ad, telefon, tarih ve saat zorunludur.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tarih formatı "YYYY-MM-DD", saat "HH:MM"
|
||||||
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
|
||||||
|
return res.status(400).json({ error: 'Geçersiz tarih formatı. YYYY-MM-DD kullanın.' });
|
||||||
|
}
|
||||||
|
if (!/^\d{2}:\d{2}$/.test(time)) {
|
||||||
|
return res.status(400).json({ error: 'Geçersiz saat formatı. HH:MM kullanın.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const startMin = toMinutes(time);
|
||||||
|
if (!withinHours(startMin)) {
|
||||||
|
return res.status(400).json({
|
||||||
|
error: `Randevular yalnızca ${String(OPEN_HOUR).padStart(2,'0')}:00 - ${String(CLOSE_HOUR).padStart(2,'0')}:00 arası alınabilir.`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Çakışma kontrolü
|
||||||
|
const list = storage.getAll();
|
||||||
|
if (isConflict(list, date, startMin, SLOT_MINUTES)) {
|
||||||
|
return res.status(409).json({ error: 'Bu saat dilimi dolu. Lütfen başka bir saat seçin.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const record = storage.add({
|
||||||
|
name,
|
||||||
|
phone,
|
||||||
|
date,
|
||||||
|
time,
|
||||||
|
service: service || 'Genel',
|
||||||
|
notes: notes || '',
|
||||||
|
duration: SLOT_MINUTES,
|
||||||
|
createdAt: new Date().toISOString()
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(201).json({ ok: true, appointment: record });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Belirli bir günün randevularını döndürür (admin)
|
||||||
|
app.get('/api/appointments', (req, res) => {
|
||||||
|
const date = req.query.date;
|
||||||
|
let list = storage.getAll();
|
||||||
|
if (date) {
|
||||||
|
list = list.filter(a => a.date === date);
|
||||||
|
}
|
||||||
|
// Tarihe ve saate göre sırala
|
||||||
|
list.sort((a, b) => (a.date + a.time).localeCompare(b.date + b.time));
|
||||||
|
res.json(list);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Belirli bir randevuyu sil (admin)
|
||||||
|
app.delete('/api/appointments/:id', (req, res) => {
|
||||||
|
const ok = storage.remove(req.params.id);
|
||||||
|
if (!ok) {
|
||||||
|
return res.status(404).json({ error: 'Randevu bulunamadı.' });
|
||||||
|
}
|
||||||
|
res.json({ ok: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Sunucuyu başlat ---
|
||||||
|
storage.init();
|
||||||
|
app.listen(PORT, () => {
|
||||||
|
console.log(`Randevu sistemi çalışıyor: http://localhost:${PORT}`);
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user