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),
),
);
}
}