← CNC-Pilot
Wprowadzenie
Poradnik Użytkownika
Pierwsze krokiZamówieniaMagazynŚledzenie czasuUżytkownicyRaporty
Dokumentacja Techniczna
Schemat bazy danychReferencja APIUwierzytelnianieMulti-tenancy
FAQVideo TutorialeDiagramy Procesów

CNC-Pilot Dokumentacja
Wersja 1.0

API Reference

CNC-Pilot używa Supabase Client API do wszystkich operacji na danych.

Uwierzytelnianie

Supabase Client

Dwa klienty zależnie od kontekstu:

Server Components & API Routes:

import { createClient } from '@/lib/supabase-server'

// Async - używa cookies
const supabase = await createClient()

Client Components:

import { supabase } from '@/lib/supabase'

// Sync - używa localStorage
const { data } = await supabase.from('orders').select('*')

Orders API

Pobranie zamówień

Lista wszystkich zamówień firmy:

const { data: orders, error } = await supabase
  .from('orders')
  .select(`
    *,
    creator:users!created_by(id, full_name)
  `)
  .eq('company_id', user.company_id)
  .order('created_at', { ascending: false })

Pojedyncze zamówienie:

const { data: order } = await supabase
  .from('orders')
  .select('*')
  .eq('id', orderId)
  .eq('company_id', user.company_id)  // Security!
  .single()

Filtrowanie po statusie:

.eq('status', 'in_progress')

Zakres dat:

.gte('deadline', '2025-01-01')
.lte('deadline', '2025-12-31')

Utworzenie zamówienia

const { data, error } = await supabase
  .from('orders')
  .insert({
    order_number: 'ZAM-2025-001',
    customer_name: 'Firma XYZ',
    part_name: 'Wałek 50x200',
    quantity: 10,
    material: 'Stal S235',
    deadline: '2025-12-31',
    status: 'pending',
    company_id: user.company_id,  // REQUIRED!
    created_by: user.id,
  })
  .select()
  .single()

Aktualizacja zamówienia

const { error } = await supabase
  .from('orders')
  .update({
    status: 'in_progress',
    notes: 'Rozpoczęto produkcję',
  })
  .eq('id', orderId)
  .eq('company_id', user.company_id)  // Security!

Usunięcie zamówienia

const { error } = await supabase
  .from('orders')
  .delete()
  .eq('id', orderId)
  .eq('company_id', user.company_id)  // Security!

Inventory API

Lista pozycji magazynowych

const { data: items } = await supabase
  .from('inventory')
  .select('*')
  .eq('company_id', user.company_id)
  .order('name')

Niski stan (low stock):

const { data: lowStock } = await supabase
  .from('inventory')
  .select('*')
  .eq('company_id', user.company_id)
  .filter('quantity', 'lte', supabase.raw('low_stock_threshold'))

Dodanie pozycji

const { data } = await supabase
  .from('inventory')
  .insert({
    sku: 'STAL-S235-50',
    name: 'Stal S235 pręt 50mm',
    category: 'stal',
    quantity: 100,
    unit: 'kg',
    low_stock_threshold: 20,
    location: 'A-12',
    company_id: user.company_id,  // REQUIRED!
    created_by: user.id,
  })

Aktualizacja stanu

// Zwiększ (przyjęcie PZ)
const { error } = await supabase.rpc('adjust_inventory', {
  item_id: itemId,
  adjustment: 50,  // +50 kg
})

// Zmniejsz (wydanie WZ)
const { error } = await supabase.rpc('adjust_inventory', {
  item_id: itemId,
  adjustment: -25,  // -25 kg
})

Time Logs API

Aktywne timery

const { data: activeTimers } = await supabase
  .from('time_logs')
  .select(`
    *,
    order:orders(id, order_number, customer_name),
    user:users(id, full_name)
  `)
  .eq('company_id', user.company_id)
  .eq('status', 'running')

Start timera

const { data: timer } = await supabase
  .from('time_logs')
  .insert({
    order_id: orderId,
    user_id: user.id,
    company_id: user.company_id,  // REQUIRED!
    start_time: new Date().toISOString(),
    status: 'running',
    hourly_rate: user.hourly_rate,  // Snapshot
  })
  .select()
  .single()

// Automatycznie zmień status zamówienia
await supabase
  .from('orders')
  .update({ status: 'in_progress' })
  .eq('id', orderId)
  .eq('company_id', user.company_id)

Stop timera

const { error } = await supabase
  .from('time_logs')
  .update({
    end_time: new Date().toISOString(),
    status: 'completed',
  })
  .eq('id', timerId)
  .eq('user_id', user.id)  // Tylko swój timer

Raport czasu

const { data: totalTime } = await supabase
  .rpc('calculate_time_worked', {
    start_date: '2025-01-01',
    end_date: '2025-01-31',
    worker_id: user.id,
  })

Users API

Lista użytkowników firmy

const { data: users } = await supabase
  .from('users')
  .select('id, full_name, email, role')
  .eq('company_id', user.company_id)
  .order('full_name')

Aktualizacja roli

Wymaga uprawnień Admin/Owner

const { error } = await supabase
  .from('users')
  .update({ role: 'operator' })
  .eq('id', userId)
  .eq('company_id', user.company_id)  // Security!

Dashboard Queries

Metryki

import { getDashboardSummary } from '@/lib/dashboard-queries'

const metrics = await getDashboardSummary(user.company_id)
// Returns:
// {
//   totalOrders: 45,
//   activeOrders: 12,
//   completedThisWeek: 8,
//   overdueOrders: 3,
//   activeTimers: 2,
//   lowStockItems: 5,
// }

Równoległe zapytania

const [orders, inventory, timers] = await Promise.all([
  supabase.from('orders').select('*').eq('company_id', companyId),
  supabase.from('inventory').select('*').eq('company_id', companyId),
  supabase.from('time_logs').select('*').eq('company_id', companyId),
])

Real-time Subscriptions

Subscribe do zmian

const channel = supabase
  .channel('orders-changes')
  .on(
    'postgres_changes',
    {
      event: '*',  // INSERT, UPDATE, DELETE
      schema: 'public',
      table: 'orders',
      filter: `company_id=eq.${user.company_id}`,
    },
    (payload) => {
      console.log('Order changed:', payload)
      // Odśwież listę
      router.refresh()
    }
  )
  .subscribe()

// Cleanup
return () => {
  supabase.removeChannel(channel)
}

Error Handling

Pattern

const { data, error } = await supabase
  .from('orders')
  .select('*')

if (error) {
  console.error('Database error:', error)
  toast.error('Nie udało się pobrać zamówień')
  return
}

// Use data

Powszechne błędy

  • Row Level Security violation - Brak company_id w query
  • Foreign key constraint - Próba usunięcia rekordu z powiązaniami
  • Unique constraint - Duplikat (np. order_number już istnieje)
  • Permission denied - Brak uprawnień (rola)

Rate Limiting

Supabase Free Tier limity:

  • 500 MB database
  • 5 GB bandwidth/miesiąc
  • 50 MB file storage
  • 500,000 Edge Function invocations/miesiąc

Pro Tier ($25/miesiąc):

  • 8 GB database
  • 250 GB bandwidth
  • 100 GB storage
  • 2M Edge Functions

Best Practices

1. Select tylko potrzebne kolumny

// ❌ Pobiera wszystko (wolne)
.select('*')

// ✅ Tylko potrzebne (szybkie)
.select('id, order_number, customer_name, status')

2. Używaj joins zamiast N+1

// ❌ N+1 queries
for (const order of orders) {
  const user = await supabase.from('users').select('*').eq('id', order.created_by).single()
}

// ✅ 1 query z join
.select(`
  *,
  creator:users!created_by(full_name)
`)

3. Indeksuj często wyszukiwane kolumny

Zobacz Database Schema - sekcja Indeksy.


Następne kroki

  • Database Schema - Pełny schemat tabel
  • Authentication - Jak działa auth
  • Multi-Tenancy - Separacja danych firm