cors di api

ALLOWED_HOSTS = ['*']
CORS_ALLOWED_ORIGINS = [
    "http://localhost:3000",
    "http://localhost:54475",
    "http://10.0.2.2:8000",
    "http://127.0.0.1:8000",   
    "http://192.168.118.247:8000",  
    'http://192.168.1.5:8000',
    'http://10.0.2.2', 
    'http://10.0.2.2:8000', 
]

main

import 'package:flutter/material.dart';
import 'screens/login_screen.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Reservasi Meja',
      home: LoginScreen(),
    );
  }
}

/lib/screens/login_Screen.dart

import 'package:flutter/material.dart';
import '../services/api_service.dart';
import 'menu_screen.dart';

class LoginScreen extends StatefulWidget {
  @override
  _LoginScreenState createState() => _LoginScreenState();
}

class _LoginScreenState extends State {
  final usernameController = TextEditingController();
  final passwordController = TextEditingController();

  void login() async {
    final token = await ApiService.login(
      usernameController.text,
      passwordController.text,
    );
    if (token != null) {
      Navigator.pushReplacement(
        context,
        MaterialPageRoute(builder: (_) => MenuScreen(token: token)),
      );
    } else {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Login gagal')),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Login')),
      body: Padding(
        padding: const EdgeInsets.all(20.0),
        child: Column(
          children: [
            TextField(controller: usernameController, decoration: InputDecoration(labelText: 'Username')),
            TextField(controller: passwordController, decoration: InputDecoration(labelText: 'Password'), obscureText: true),
            SizedBox(height: 20),
            ElevatedButton(onPressed: login, child: Text('Login')),
          ],
        ),
      ),
    );
  }
}

screens/menu_screen

import 'package:flutter/material.dart';
import '../services/api_service.dart';
import '../models/menu_models.dart';

class MenuScreen extends StatelessWidget {
  final String token;
  const MenuScreen({super.key, required this.token});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Daftar Menu')),
      body: FutureBuilder>(
        future: ApiService.getMenu(token),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) return Center(child: CircularProgressIndicator());
          if (!snapshot.hasData || snapshot.data!.isEmpty) return Center(child: Text('Tidak ada menu'));
          final menuList = snapshot.data!;

          return ListView(
            children: [
              ...menuList.map((m) => ListTile(
                    title: Text(m.nama_menu),
                    subtitle: Text('Rp ${m.harga}'),
                  )),

            ],
          );
        },
      ),
    );
  }
}

/services/api_service

import 'dart:convert';
import 'package:http/http.dart' as http;
import '../models/menu_models.dart';

class ApiService {
  static const baseUrl = 'http://10.0.2.2:8000/api';

  static Future login(String username, String password) async {
    final response = await http.post(
      Uri.parse('$baseUrl/login/'),
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode({'username': username, 'password': password}),
    );
    if (response.statusCode == 200) {
      final json = jsonDecode(response.body);
      return json['access'];
    }
    return null;
  }

  static Future> getMenu(String token) async {
    final response = await http.get(
      Uri.parse('$baseUrl/menu/'),
      headers: {'Authorization': 'Bearer $token'},
    );
    if (response.statusCode == 200) {
      return (jsonDecode(response.body) as List)
          .map((e) => MenuModel.fromJson(e))
          .toList();
    }
    throw Exception('Failed to load menus');

  }

  // static Future> getMeja(String token) async {
  //   final response = await http.get(
  //     Uri.parse('$baseUrl/meja/'),
  //     headers: {'Authorization': 'Bearer $token'},
  //   );
  //   if (response.statusCode == 200) {
  //     final List data = jsonDecode(response.body);
  //     return data.map((e) => MejaModel.fromJson(e)).toList();
  //   }
  //   return [];
  // }

  static Future updateMeja(String token, int id, String status) async {
    await http.patch(
      Uri.parse('$baseUrl/meja/$id/'),
      headers: {
        'Authorization': 'Bearer $token',
        'Content-Type': 'application/json',
      },
      body: jsonEncode({"status": status}),
    );
  }
}

models/menu_models

class MenuModel {
  final int id_menu;
  final String nama_menu;
  final double harga;
  final int stok;
  final String foto;
  MenuModel(
      {required this.id_menu,
      required this.nama_menu,
      required this.harga,
      required this.foto,
      required this.stok});

  factory MenuModel.fromJson(Map json) {
    return MenuModel(
        id_menu: json['id_menu'],
        nama_menu: json['nama_menu'],
        harga: double.tryParse(json['harga'].toString()) ?? 0.0,
        stok: json['stok'],
        foto: json['foto']);
  }
  Map toJson() => {
        'id_menu': id_menu,
        'nama_menu': nama_menu,
        'harga': harga,
        'stok': stok,
        'foto': foto,
      };
}

// ==== models/meja_model.dart ====
class MejaModel {
  final int? id;
  final int noMeja;
  final int kapasitas;
  final String status;

  MejaModel(
      {this.id,
      required this.noMeja,
      required this.kapasitas,
      required this.status});

  factory MejaModel.fromJson(Map json) {
    return MejaModel(
      id: json['id'],
      noMeja: json['no_meja'],
      kapasitas: json['kapasitas'],
      status: json['status'],
    );
  }
}

menu extend

import 'package:flutter/material.dart';
import '../services/api_service.dart';
import '../models/menu_models.dart';

class MenuScreen extends StatelessWidget {
  final String token;
  const MenuScreen({super.key, required this.token});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Menu Makanan')),
      body: FutureBuilder>(
        future: ApiService.getMenu(token),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) return Center(child: CircularProgressIndicator());
          if (!snapshot.hasData || snapshot.data!.isEmpty) return Center(child: Text('Tidak ada menu tersedia'));

          final menuList = snapshot.data!;

          return Padding(
            padding: const EdgeInsets.all(12.0),
            child: GridView.builder(
              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 2,
                crossAxisSpacing: 12,
                mainAxisSpacing: 12,
                childAspectRatio: 0.75,
              ),
              itemCount: menuList.length,
              itemBuilder: (context, index) {
                final m = menuList[index];
                return Card(
                  elevation: 4,
                  shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      // Foto Menu
                      ClipRRect(
                        borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
                        child: Image.network(
                          m.foto,
                          height: 110,
                          width: double.infinity,
                          fit: BoxFit.cover,
                          errorBuilder: (context, error, stackTrace) => Container(
                            height: 110,
                            color: Colors.grey[300],
                            child: Icon(Icons.image_not_supported),
                          ),
                        ),
                      ),
                      Padding(
                        padding: const EdgeInsets.all(8.0),
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Text(
                              m.nama_menu,
                              style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
                            ),
                            SizedBox(height: 4),
                            Text('Rp ${m.harga.toStringAsFixed(0)}'),
                            Text('Stok: ${m.stok}'),
                            SizedBox(height: 8),
                            ElevatedButton(
                              onPressed: () {
                                // TODO: Tambahkan logika tambah ke keranjang atau pesan
                                ScaffoldMessenger.of(context).showSnackBar(
                                  SnackBar(content: Text('${m.nama_menu} ditambahkan ke pesanan')),
                                );
                              },
                              style: ElevatedButton.styleFrom(
                                minimumSize: Size(double.infinity, 36),
                                backgroundColor: Colors.green,
                              ),
                              child: Text('Pesan'),
                            ),
                          ],
                        ),
                      ),
                    ],
                  ),
                );
              },
            ),
          );
        },
      ),

      floatingActionButton: FloatingActionButton.extended(
        onPressed: () {
          // TODO: Buka halaman keranjang
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(content: Text('Menuju halaman keranjang...')),
          );
        },
        label: Text('Keranjang'),
        icon: Icon(Icons.shopping_cart),
      ),
    );
  }
}