Client Examples

Real-world automation projects built with Half-Stack approach

ตัวอย่างโปรเจกต์จริงที่ ShantiLink ได้สร้างให้ลูกค้าด้วย Half-Stack Development

Project Overview

Our Approach

Half-Stack Development คือ:
├─ n8n สำหรับ workflow และ integrations
├─ AI Assistant ช่วยเขียนโค้ด
├─ Custom code สำหรับ business logic
├─ Cloud services สำหรับ deployment
└─ Real-time features สำหรับ user experience

Technology Stack

Frontend: Next.js + TailwindCSS (Vercel)
Backend: Node.js/TypeScript (Vercel Functions)
Database: Supabase (PostgreSQL)
Automation: n8n (Railway)
Authentication: LINE Login + Supabase Auth
Real-time: Supabase Real-time + n8n Webhooks

Example 1: Restaurant Chain Order System

Client Background

ลูกค้า: ร้านอาหารชื่อดังในกรุงเทพฯ
จำนวนสาขา: 5 สาขา
ปัญหา: การรับ order ผ่าน LINE ยุ่งยาก
ระบบเก่า: จดลงกระดาษ ทำให้ผิดพลาดบ่อย

Solution Architecture

LINE OA → n8n → Custom API → Supabase → Dashboard → Kitchen Display

Features Implemented

✅ LINE Chatbot รับ order
├─ Thai NLP สำหรับสั่งอาหาร
├─ Menu recommendations
├─ Order confirmation
└─ Real-time status updates

✅ Order Management System
├─ Auto-generate order ID
├─ Split orders by branch
├─ Inventory management
└─ Payment integration

✅ Real-time Dashboard
├─ Live order tracking
├─ Sales analytics
├─ Popular items
└─ Peak hours analysis

✅ Kitchen Display System
├─ Order queue
├─ Preparation timer
├─ Completion notification
└─ Branch coordination

Technical Implementation

LINE Chatbot with Thai NLP

// Thai food ordering NLP
const foodKeywords = {
  rice: ['ข้าว', 'กะเพรา', 'ผัด', 'ราดหน้า'],
  noodles: ['ก๋วยเตี๋ยว', 'บะหมี่', 'เส้น', 'ราเมง'],
  drinks: ['น้ำ', 'เย็น', 'ชา', 'กาแฟ', 'โอเลี้ยง'],
  spicy: ['เผ็ด', 'พริก', 'ชาย', 'ต้มยำ'],
  not_spicy: ['ไม่เผ็ด', 'น้อย', 'หวาน']
};

function parseFoodOrder(message) {
  const items = [];
  const normalizedMessage = message.toLowerCase();
  
  // Detect food items
  for (const [category, keywords] of Object.entries(foodKeywords)) {
    const matches = keywords.filter(keyword => 
      normalizedMessage.includes(keyword)
    );
    
    if (matches.length > 0) {
      items.push({
        category,
        keywords: matches,
        confidence: matches.length / keywords.length
      });
    }
  }
  
  // Extract quantity
  const quantities = normalizedMessage.match(/\d+/g) || [];
  
  return {
    items,
    quantities,
    spicy_level: detectSpicyLevel(normalizedMessage),
    special_instructions: extractInstructions(normalizedMessage)
  };
}

Order Processing Workflow

// n8n Function Node - Order Processing
async function processRestaurantOrder() {
  const orderData = $json;
  const parsedOrder = parseFoodOrder(orderData.message);
  
  // Get menu items from database
  const menuItems = await getMenuItems(parsedOrder.items);
  
  // Calculate total
  const totalAmount = menuItems.reduce((sum, item) => 
    sum + (item.price * item.quantity), 0
  );
  
  // Generate order ID
  const orderId = `RES${Date.now()}${Math.random().toString(36).substr(2, 5).toUpperCase()}`;
  
  // Save to database
  const order = {
    order_id: orderId,
    customer_id: orderData.userId,
    items: menuItems,
    total_amount: totalAmount,
    branch: determineBranch(orderData.userLocation),
    status: 'pending',
    created_at: new Date().toISOString()
  };
  
  // Send to kitchen display
  await sendToKitchenDisplay(order);
  
  // Send confirmation to customer
  await sendOrderConfirmation(orderData.replyToken, order);
  
  return [{ json: order }];
}

Real-time Dashboard

// Dashboard component for restaurant
export default function RestaurantDashboard() {
  const [orders, setOrders] = useState([]);
  const [stats, setStats] = useState({
    todayOrders: 0,
    todayRevenue: 0,
    pendingOrders: 0,
    avgPrepTime: 0
  });

  useEffect(() => {
    // Real-time subscription
    const subscription = supabase
      .channel('orders')
      .on('postgres_changes', 
        { event: '*', schema: 'public', table: 'orders' },
        (payload) => {
          if (payload.eventType === 'INSERT') {
            setOrders(prev => [payload.new, ...prev].slice(0, 50));
          } else if (payload.eventType === 'UPDATE') {
            setOrders(prev => prev.map(order => 
              order.id === payload.new.id ? payload.new : order
            ));
          }
          updateStats();
        }
      )
      .subscribe();

    return () => subscription.unsubscribe();
  }, []);

  return (
    <div className="p-6">
      <h1 className="text-3xl font-bold mb-8">Restaurant Dashboard</h1>
      
      {/* Stats Cards */}
      <div className="grid grid-cols-4 gap-6 mb-8">
        <StatCard title="Today Orders" value={stats.todayOrders} />
        <StatCard title="Revenue" value={`฿${stats.todayRevenue}`} />
        <StatCard title="Pending" value={stats.pendingOrders} />
        <StatCard title="Avg Prep Time" value={`${stats.avgPrepTime}m`} />
      </div>

      {/* Orders Table */}
      <div className="bg-white rounded-lg shadow">
        <OrderTable orders={orders} />
      </div>
    </div>
  );
}

Results

✅ ผลลัพธ์หลัง 3 เดือน:
├─ Order errors ลด 85%
├─ ระยะเวลาเสิร์� ลด 30%
├─ ลูกค้าพึงพอใจเพิ่ม 40%
├─ พนักงานทำงานง่ายขึ้น
└─ สามารถรองรับลูกค้าเพิ่ม 3 เท่า

Example 2: Clinic Appointment System

Client Background

ลูกค้า: คลินิกทันตกรรมในกรุงเทพฯ
จำนวนแพทย์: 3 คน
ปัญหา: การนัดหมายผ่านโทรศัพท์ยุ่งยาก
ระบบเก่า: สมุดบันทึก คนเดียวทำทั้งหมด

Solution Architecture

LINE OA → n8n → Custom API → Supabase → Google Calendar → LINE Confirmation

Features Implemented

✅ Smart Appointment Booking
├─ Available time slots
├─ Doctor specialization matching
├─ Thai date/time understanding
└─ Conflict detection

✅ Automated Reminders
├─ 1 day before appointment
├─ 1 hour before appointment
├─ Reschedule options
└─ Cancellation handling

✅ Doctor Dashboard
├─ Daily schedule
├─ Patient history
├─ Treatment notes
└─ Analytics

✅ Integration with Google Calendar
├─ Sync with personal calendars
├─ Block unavailable times
├─ Mobile access
└─ Notifications

Technical Implementation

Appointment Booking Logic

// Smart appointment booking
async function bookAppointment(userId, requestedTime, doctorId) {
  // Parse Thai time expressions
  const parsedTime = parseThaiTime(requestedTime);
  
  // Get available slots
  const availableSlots = await getAvailableSlots(doctorId, parsedTime.date);
  
  // Find best match
  const bestSlot = findBestSlot(availableSlots, parsedTime);
  
  if (!bestSlot) {
    // Suggest alternatives
    const alternatives = await findAlternatives(doctorId, parsedTime);
    return {
      success: false,
      message: 'ไม่มีเวลาว่างครับ',
      alternatives: alternatives
    };
  }
  
  // Create appointment
  const appointment = {
    customer_id: userId,
    doctor_id: doctorId,
    appointment_date: bestSlot.date,
    appointment_time: bestSlot.time,
    status: 'confirmed',
    created_at: new Date().toISOString()
  };
  
  // Save to database
  const { data, error } = await supabase
    .from('appointments')
    .insert(appointment)
    .select()
    .single();
  
  if (error) throw error;
  
  // Add to Google Calendar
  await addToGoogleCalendar(appointment);
  
  // Schedule reminders
  await scheduleReminders(appointment);
  
  return { success: true, appointment: data };
}

// Parse Thai time expressions
function parseThaiTime(timeExpression) {
  const today = new Date();
  const tomorrow = new Date(today);
  tomorrow.setDate(today.getDate() + 1);
  
  const patterns = {
    'วันนี้': today,
    'พรุ่งนี้': tomorrow,
    'มะรืน': new Date(today.getTime() + 2 * 24 * 60 * 60 * 1000),
    'เช้า': { hour: 9, minute: 0 },
    'บ่าย': { hour: 13, minute: 0 },
    'เย็น': { hour: 16, minute: 0 }
  };
  
  let date = today;
  let time = { hour: 10, minute: 0 };
  
  for (const [pattern, value] of Object.entries(patterns)) {
    if (timeExpression.includes(pattern)) {
      if (value instanceof Date) {
        date = value;
      } else {
        time = value;
      }
    }
  }
  
  // Extract specific time
  const timeMatch = timeExpression.match(/(\d{1,2})[:.]?(\d{2})/);
  if (timeMatch) {
    time.hour = parseInt(timeMatch[1]);
    time.minute = parseInt(timeMatch[2]);
  }
  
  date.setHours(time.hour, time.minute, 0, 0);
  
  return { date, time };
}

Automated Reminder System

// n8n Workflow - Appointment Reminders
async function sendAppointmentReminders() {
  const tomorrow = new Date();
  tomorrow.setDate(tomorrow.getDate() + 1);
  tomorrow.setHours(0, 0, 0, 0);
  
  const dayAfter = new Date(tomorrow);
  dayAfter.setDate(dayAfter.getDate() + 1);
  
  // Get appointments for tomorrow
  const { data: appointments } = await supabase
    .from('appointments')
    .select(`
      *,
      customers (line_user_id, display_name),
      doctors (name, specialization)
    `)
    .gte('appointment_date', tomorrow.toISOString())
    .lt('appointment_date', dayAfter.toISOString())
    .eq('status', 'confirmed');
  
  for (const appointment of appointments) {
    // Send LINE reminder
    const message = `
🦷 แจ้งเตือนนัดหมายครับ

วันที่: ${formatThaiDate(appointment.appointment_date)}
เวลา: ${formatThaiTime(appointment.appointment_time)}
แพทย์: คุณหมอ${appointment.doctors.name}
แผนก: ${appointment.doctors.specialization}

📍 ที่อยู่คลินิก: 123 ถนนสุขุมวิท กรุงเทพฯ
📱 โทร: 02-123-4567

⏰ กรุณามาก่อนเวลานัด 15 นาทีครับ
❌ หากต้องการเลื่อน/ยกเลิก แจ้งล่วงหน้า 2 ชั่วโมง
    `;
    
    await sendLINEMessage(appointment.customers.line_user_id, message);
  }
}

// Schedule this function to run daily at 6 PM
// In n8n: Cron Trigger → Function Node → LINE Sender

Results

✅ ผลลัพธ์หลัง 2 เดือน:
├─ No-show rate ลด 60%
├─ Booking errors ลด 90%
├─ แพทย์มีเวลาว่างเพิ่ม 20%
├─ ลูกค้าพึงพอใจเพิ่ม 50%
└─ พนักงานประหยัดเวลา 10 ชั่วโมง/สัปดาห์

Example 3: E-commerce Inventory System

Client Background

ลูกค้า: ร้านค้าออนไลน์ขายสินค้าแฟชั่น
แพลตฟอร์ม: Shopee, Lazada, Facebook
ปัญหา: สต็อกไม่ตรงกันทุกแพลตฟอร์ม
ระบบเก่า: อัปเดตแยกกันทุกแพลตฟอร์ม

Solution Architecture

Multiple Platforms → Webhooks → n8n → Custom API → Supabase → Sync Back

Features Implemented

✅ Multi-Platform Sync
├─ Real-time inventory updates
├─ Order consolidation
├─ Price synchronization
└─ Product information sync

✅ Automated Reorder System
├─ Low stock alerts
├─ Supplier notifications
├─ Purchase order generation
└─ Budget tracking

✅ Analytics Dashboard
├─ Sales by platform
├─ Popular products
├─ Inventory turnover
└─ Profit margins

✅ Customer Service Integration
├─ Order status updates
├─ Shipping notifications
├─ Return handling
└─ Customer inquiries

Technical Implementation

Multi-Platform Sync

// Inventory synchronization
class InventorySync {
  constructor() {
    this.platforms = {
      shopee: new ShopeeAPI(),
      lazada: new LazadaAPI(),
      facebook: new FacebookAPI()
    };
  }

  async syncProduct(productId) {
    // Get master inventory from Supabase
    const { data: product } = await supabase
      .from('products')
      .select('*')
      .eq('id', productId)
      .single();

    // Update all platforms
    const updatePromises = Object.entries(this.platforms).map(
      async ([platform, api]) => {
        try {
          await api.updateProduct({
            platform_product_id: product[`${platform}_product_id`],
            stock: product.stock,
            price: product.price,
            description: product.description_th
          });
          
          console.log(`Updated ${platform} successfully`);
        } catch (error) {
          console.error(`Failed to update ${platform}:`, error);
          
          // Log for manual review
          await this.logSyncError(productId, platform, error);
        }
      }
    );

    await Promise.allSettled(updatePromises);
  }

  async handleOrder(orderData) {
    const platform = orderData.platform;
    const items = orderData.items;

    // Update inventory for each item
    for (const item of items) {
      await this.updateInventory(item.product_id, -item.quantity);
      
      // Trigger sync to other platforms
      await this.syncProduct(item.product_id);
    }

    // Save order to database
    await this.saveOrder(orderData);

    // Send notifications
    await this.sendOrderNotification(orderData);
  }

  async updateInventory(productId, quantityChange) {
    const { data: product, error } = await supabase
      .from('products')
      .select('stock, reorder_level')
      .eq('id', productId)
      .single();

    if (error) throw error;

    const newStock = product.stock + quantityChange;
    
    // Update stock
    await supabase
      .from('products')
      .update({ 
        stock: newStock,
        updated_at: new Date().toISOString()
      })
      .eq('id', productId);

    // Check reorder level
    if (newStock <= product.reorder_level) {
      await this.triggerReorder(productId, newStock);
    }
  }

  async triggerReorder(productId, currentStock) {
    // Get supplier info
    const { data: product } = await supabase
      .from('products')
      .select(`
        *,
        suppliers (name, contact_email, lead_time_days)
      `)
      .eq('id', productId)
      .single();

    // Generate purchase order
    const purchaseOrder = {
      product_id: productId,
      quantity: product.reorder_quantity,
      supplier_id: product.supplier_id,
      urgent: currentStock < 5,
      created_at: new Date().toISOString()
    };

    // Save purchase order
    await supabase
      .from('purchase_orders')
      .insert(purchaseOrder);

    // Notify supplier
    await this.emailSupplier({
      to: product.suppliers.contact_email,
      subject: `สั่งซื้อสินค้า ${product.name_th}`,
      body: `
        สวัสดีครับ/ค่ะ ${product.suppliers.name},
        
        ต้องการสั่งซื้อสินค้า:
        - รหัสสินค้า: ${product.sku}
        - ชื่อสินค้า: ${product.name_th}
        - จำนวน: ${product.reorder_quantity}
        - ความเร่งด่วน: ${currentStock < 5 ? 'ด่วน' : 'ปกติ'}
        
        กรุณายืนยันภายใน 24 ชั่วโมง
      `
    });

    // Notify admin
    await this.sendAdminNotification({
      message: `📦 สต็อกสินค้า ${product.name_th} เหลือน้อย ต้องการสั่งซื้อเพิ่ม`,
      productId: productId,
      currentStock: currentStock
    });
  }
}

Analytics Dashboard

// E-commerce analytics dashboard
export default function EcommerceDashboard() {
  const [analytics, setAnalytics] = useState({
    totalSales: 0,
    ordersByPlatform: {},
    topProducts: [],
    lowStock: [],
    profitMargin: 0
  });

  useEffect(() => {
    fetchAnalytics();
    
    // Real-time updates
    const subscription = supabase
      .channel('analytics')
      .on('postgres_changes',
        { event: 'INSERT', schema: 'public', table: 'orders' },
        () => fetchAnalytics()
      )
      .subscribe();

    return () => subscription.unsubscribe();
  }, []);

  async function fetchAnalytics() {
    const today = new Date();
    const thirtyDaysAgo = new Date(today.getTime() - 30 * 24 * 60 * 60 * 1000);

    const [
      salesResult,
      platformResult,
      productsResult,
      stockResult
    ] = await Promise.all([
      // Total sales
      supabase
        .from('orders')
        .select('total_amount')
        .gte('created_at', thirtyDaysAgo.toISOString()),
      
      // Orders by platform
      supabase
        .from('orders')
        .select('platform, total_amount')
        .gte('created_at', thirtyDaysAgo.toISOString()),
      
      // Top products
      supabase
        .rpc('get_top_products', { 
          start_date: thirtyDaysAgo.toISOString(),
          limit: 10 
        }),
      
      // Low stock
      supabase
        .from('products')
        .select('name_th, stock, reorder_level')
        .lt('stock', 'reorder_level * 2')
    ]);

    const totalSales = salesResult.data?.reduce((sum, order) => 
      sum + order.total_amount, 0
    ) || 0;

    const ordersByPlatform = platformResult.data?.reduce((acc, order) => {
      acc[order.platform] = (acc[order.platform] || 0) + order.total_amount;
      return acc;
    }, {}) || {};

    setAnalytics({
      totalSales,
      ordersByPlatform,
      topProducts: productsResult.data || [],
      lowStock: stockResult.data || [],
      profitMargin: calculateProfitMargin(salesResult.data)
    });
  }

  return (
    <div className="p-6">
      <h1 className="text-3xl font-bold mb-8">E-commerce Dashboard</h1>
      
      {/* Overview Cards */}
      <div className="grid grid-cols-4 gap-6 mb-8">
        <MetricCard 
          title="30-Day Sales" 
          value={`฿${analytics.totalSales.toLocaleString()}`} 
          change="+15%" 
        />
        <MetricCard 
          title="Orders" 
          value={Object.values(analytics.ordersByPlatform).reduce((a, b) => a + b, 0)} 
          change="+8%" 
        />
        <MetricCard 
          title="Profit Margin" 
          value={`${analytics.profitMargin}%`} 
          change="+2%" 
        />
        <MetricCard 
          title="Low Stock Items" 
          value={analytics.lowStock.length} 
          change="-5" 
        />
      </div>

      {/* Charts */}
      <div className="grid grid-cols-2 gap-6 mb-8">
        <PlatformSalesChart data={analytics.ordersByPlatform} />
        <TopProductsChart data={analytics.topProducts} />
      </div>

      {/* Low Stock Alert */}
      <LowStockAlert items={analytics.lowStock} />
    </div>
  );
}

Results

✅ ผลลัพธ์หลัง 1 เดือน:
├─ Out-of-stock ลด 95%
├─ ขายได้มากขึ้น 25%
├─ ประหยัดเวลาอัปเดต 20 ชั่วโมง/สัปดาห์
├─ Customer satisfaction เพิ่ม 30%
└─ สามารถขยายแพลตฟอร์มได้ง่าย

Key Success Factors

1. Understanding Thai Context

✅ Thai Language Processing
├─ ศัพท์เฉพาะทาง (อาหาร, การแพทย์, แฟชั่น)
├─ วิธีการสื่อสารของคนไทย
├─ Culture-specific features
└─ Local payment methods

✅ Business Process Understanding
├─ ทำความเข้าใจ workflow จริง
├─ ปรับเปลี่ยนตามความต้องการ
├─ ทดสอบและปรับปรุง
└─ สนับสนุนการเปลี่ยนแปลง

2. Technology Choices

✅ Right Tools for Right Job
├─ n8n: Automation และ integrations
├─ Supabase: Database และ real-time
├─ Vercel: Frontend และ serverless
├─ AI: Code generation และ optimization
└─ LINE: Platform ที่คนไทยใช้มากสุด

✅ Scalability Considerations
├─ ออกแบบให้รองรับการเติบโต
├─ ใช้ services ที่เหมาะสม
├─ Monitoring และ analytics
└─ Cost-effective solutions

3. Development Process

✅ Agile Approach
├─ MVP ก่อน ค่อยๆ พัฒนา
├─ User feedback ทุก sprint
├─ Continuous deployment
└─ Rapid iteration

✅ Quality Assurance
├─ Testing บน production data
├─ Error handling ที่ครอบคลุม
├─ Monitoring ตลอดเวลา
└─ Backup และ recovery plans

Lessons Learned

1. Technical Lessons

✅ Do's:
├─ เริ่มจาก n8n ก่อนเสมอ
├─ ใช้ AI assistant อย่างมีประสิทธิภาพ
├─ ทดสอบกับข้อมูลจริง
├─ วางแผน scalability ตั้งแต่แรก
└─ Document ทุกอย่าง

❌ Don'ts:
├─ ไม่ตรวจสอบความถูกต้องของโค้ด AI
├─ ลืม error handling
├─ ไม่คิดถึง performance
├─ ข้าม security considerations
└─ ไม่ทดสอบกับผู้ใช้จริง

2. Business Lessons

✅ Success Factors:
├─ ฟังลูกค้าให้มาก
├─ เข้าใจ business process ลึกซึ้ง
├─ สร้าง trust กับลูกค้า
├─ ส่งมอบ value อย่างรวดเร็ว
└─ สนับสนุนหลังการขายดี

❌ Common Mistakes:
├─ over-engineer ในช่วงแรก
├─ ไม่สื่อสารกับลูกค้าบ่อยพอ
├─ ไม่มี backup plan
├─ ไม่คิดถึง maintenance
└─ ใช้ technology ซับซ้อนเกินไป

Next Steps for Your Business

1. Assessment

คำถามที่ต้องถามตัวเอง:
├─ ธุรกิจมีปัญหาอะไรที่ automation ช่วยได้?
├─ ลูกค้า/พนักงานใช้ LINE หรือไม่?
├─ มีข้อมูลที่ต้อง sync หลายที่ไหม?
├─ ต้องการระบบ real-time หรือไม่?
└─ งบประมาณสำหรับเริ่มต้นเท่าไหร่?

2. Getting Started

ขั้นตอนการเริ่มต้น:
├─ 1️⃣ ปรึกษากับ expert (เรา!)
├─ 2️⃣ วิเคราะห์และวางแผน
├─ 3️⃣ สร้าง prototype (1-2 สัปดาห์)
├─ 4️⃣ ทดสอบกับผู้ใช้จริง
├─ 5️⃣ พัฒนาและ deploy
└─ 6️⃣ ฝึกอบรมและสนับสนุน

3. Investment

การลงทุนที่คาดหวัง:
├─ เวลา: 2-8 สัปดาห์ (ขึ้นอยู่กับความซับซ้อน)
├─ งบ: 10,000-100,000 บาท (setup fee)
├─ ค่าบำรุงรักษา: 2,000-10,000 บาท/เดือน
└─ ROI: 6-12 เดือน (ประหยัดต้นทุน + เพิ่มรายได้)

พร้อมเริ่มโปรเจกต์ของคุณหรือไม่? ติดต่อเราได้ที่ ShantiLink.com 💬

ดูตัวอย่างเพิ่มเติม: