fitur order paket
This commit is contained in:
@ -1,54 +1,156 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
class PaketModel {
|
class PaketModel {
|
||||||
final String? id;
|
final String id;
|
||||||
final String? nama;
|
final String nama;
|
||||||
final String? deskripsi;
|
final String deskripsi;
|
||||||
final int? harga;
|
final double harga;
|
||||||
final int? kuantitas;
|
final int kuantitas;
|
||||||
final String? foto_paket;
|
final List<String> foto;
|
||||||
final List<dynamic>? satuanWaktuSewa;
|
final List<Map<String, dynamic>> satuanWaktuSewa;
|
||||||
|
final DateTime createdAt;
|
||||||
|
final DateTime updatedAt;
|
||||||
|
final String? foto_paket; // Main photo URL
|
||||||
|
final List<String>? images; // List of photo URLs
|
||||||
|
|
||||||
PaketModel({
|
PaketModel({
|
||||||
this.id,
|
required this.id,
|
||||||
this.nama,
|
required this.nama,
|
||||||
this.deskripsi,
|
required this.deskripsi,
|
||||||
this.harga,
|
required this.harga,
|
||||||
this.kuantitas,
|
required this.kuantitas,
|
||||||
|
required this.foto,
|
||||||
|
required this.satuanWaktuSewa,
|
||||||
this.foto_paket,
|
this.foto_paket,
|
||||||
this.satuanWaktuSewa,
|
this.images,
|
||||||
|
required this.createdAt,
|
||||||
|
required this.updatedAt,
|
||||||
});
|
});
|
||||||
|
|
||||||
Map<String, dynamic> toMap() {
|
// Alias for fromJson to maintain compatibility
|
||||||
return {
|
factory PaketModel.fromMap(Map<String, dynamic> json) => PaketModel.fromJson(json);
|
||||||
'id': id,
|
|
||||||
'nama': nama,
|
factory PaketModel.fromJson(Map<String, dynamic> json) {
|
||||||
'deskripsi': deskripsi,
|
// Handle different possible JSON structures
|
||||||
'harga': harga,
|
final fotoList = <String>[];
|
||||||
'kuantitas': kuantitas,
|
|
||||||
'foto_paket': foto_paket,
|
// Check for different possible photo field names
|
||||||
'satuanWaktuSewa': satuanWaktuSewa,
|
if (json['foto'] != null) {
|
||||||
};
|
if (json['foto'] is String) {
|
||||||
}
|
fotoList.add(json['foto']);
|
||||||
|
} else if (json['foto'] is List) {
|
||||||
factory PaketModel.fromMap(Map<String, dynamic> map) {
|
fotoList.addAll((json['foto'] as List).whereType<String>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json['foto_paket'] != null) {
|
||||||
|
if (json['foto_paket'] is String) {
|
||||||
|
fotoList.add(json['foto_paket']);
|
||||||
|
} else if (json['foto_paket'] is List) {
|
||||||
|
fotoList.addAll((json['foto_paket'] as List).whereType<String>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle satuan_waktu_sewa
|
||||||
|
List<Map<String, dynamic>> satuanWaktuList = [];
|
||||||
|
if (json['satuan_waktu_sewa'] != null) {
|
||||||
|
if (json['satuan_waktu_sewa'] is List) {
|
||||||
|
satuanWaktuList = List<Map<String, dynamic>>.from(
|
||||||
|
json['satuan_waktu_sewa'].map((x) => x is Map ? Map<String, dynamic>.from(x) : {})
|
||||||
|
);
|
||||||
|
} else if (json['satuan_waktu_sewa'] is Map) {
|
||||||
|
satuanWaktuList = [Map<String, dynamic>.from(json['satuan_waktu_sewa'])];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return PaketModel(
|
return PaketModel(
|
||||||
id: map['id'],
|
id: json['id']?.toString() ?? '',
|
||||||
nama: map['nama'],
|
nama: json['nama']?.toString() ?? '',
|
||||||
deskripsi: map['deskripsi'],
|
deskripsi: json['deskripsi']?.toString() ?? '',
|
||||||
harga: map['harga']?.toInt(),
|
harga: (json['harga'] is num) ? (json['harga'] as num).toDouble() : 0.0,
|
||||||
kuantitas: map['kuantitas']?.toInt(),
|
kuantitas: (json['kuantitas'] is num) ? (json['kuantitas'] as num).toInt() : 1,
|
||||||
foto_paket: map['foto_paket'],
|
foto: fotoList,
|
||||||
satuanWaktuSewa: map['satuanWaktuSewa'],
|
satuanWaktuSewa: satuanWaktuList,
|
||||||
|
foto_paket: json['foto_paket']?.toString(),
|
||||||
|
images: json['images'] != null ? List<String>.from(json['images']) : null,
|
||||||
|
createdAt: json['created_at'] != null
|
||||||
|
? DateTime.parse(json['created_at'].toString())
|
||||||
|
: DateTime.now(),
|
||||||
|
updatedAt: json['updated_at'] != null
|
||||||
|
? DateTime.parse(json['updated_at'].toString())
|
||||||
|
: DateTime.now(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String toJson() => json.encode(toMap());
|
// Convert to JSON
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'id': id,
|
||||||
|
'nama': nama,
|
||||||
|
'deskripsi': deskripsi,
|
||||||
|
'harga': harga,
|
||||||
|
'kuantitas': kuantitas,
|
||||||
|
'foto': foto,
|
||||||
|
'foto_paket': foto_paket,
|
||||||
|
'images': images,
|
||||||
|
'satuan_waktu_sewa': satuanWaktuSewa,
|
||||||
|
'created_at': createdAt.toIso8601String(),
|
||||||
|
'updated_at': updatedAt.toIso8601String(),
|
||||||
|
};
|
||||||
|
|
||||||
factory PaketModel.fromJson(String source) => PaketModel.fromMap(json.decode(source));
|
// Create a copy of the model with some fields updated
|
||||||
|
PaketModel copyWith({
|
||||||
@override
|
String? id,
|
||||||
String toString() {
|
String? nama,
|
||||||
return 'PaketModel(id: $id, nama: $nama, deskripsi: $deskripsi, harga: $harga, kuantitas: $kuantitas, foto_paket: $foto_paket, satuanWaktuSewa: $satuanWaktuSewa)';
|
String? deskripsi,
|
||||||
|
double? harga,
|
||||||
|
int? kuantitas,
|
||||||
|
List<String>? foto,
|
||||||
|
List<Map<String, dynamic>>? satuanWaktuSewa,
|
||||||
|
String? foto_paket,
|
||||||
|
List<String>? images,
|
||||||
|
DateTime? createdAt,
|
||||||
|
DateTime? updatedAt,
|
||||||
|
}) {
|
||||||
|
return PaketModel(
|
||||||
|
id: id ?? this.id,
|
||||||
|
nama: nama ?? this.nama,
|
||||||
|
deskripsi: deskripsi ?? this.deskripsi,
|
||||||
|
harga: harga ?? this.harga,
|
||||||
|
kuantitas: kuantitas ?? this.kuantitas,
|
||||||
|
foto: foto ?? List.from(this.foto),
|
||||||
|
satuanWaktuSewa: satuanWaktuSewa ?? List.from(this.satuanWaktuSewa),
|
||||||
|
foto_paket: foto_paket ?? this.foto_paket,
|
||||||
|
images: images ?? (this.images != null ? List.from(this.images!) : null),
|
||||||
|
createdAt: createdAt ?? this.createdAt,
|
||||||
|
updatedAt: updatedAt ?? this.updatedAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the first photo URL or a placeholder
|
||||||
|
String get firstPhotoUrl => foto.isNotEmpty ? foto.first : '';
|
||||||
|
|
||||||
|
// Get the formatted price
|
||||||
|
String get formattedPrice => 'Rp${harga.toStringAsFixed(0).replaceAllMapped(
|
||||||
|
RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'),
|
||||||
|
(Match m) => '${m[1]}.',
|
||||||
|
)}';
|
||||||
|
|
||||||
|
// Check if the package is available
|
||||||
|
bool get isAvailable => kuantitas > 0;
|
||||||
|
|
||||||
|
// Get the first available time unit
|
||||||
|
Map<String, dynamic>? get defaultTimeUnit =>
|
||||||
|
satuanWaktuSewa.isNotEmpty ? satuanWaktuSewa.first : null;
|
||||||
|
|
||||||
|
// Get the price for a specific time unit
|
||||||
|
double getPriceForTimeUnit(String timeUnitId) {
|
||||||
|
try {
|
||||||
|
final unit = satuanWaktuSewa.firstWhere(
|
||||||
|
(unit) => unit['id'] == timeUnitId || unit['id'].toString() == timeUnitId,
|
||||||
|
);
|
||||||
|
return (unit['harga'] as num?)?.toDouble() ?? 0.0;
|
||||||
|
} catch (e) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -846,28 +846,6 @@ class AsetProvider extends GetxService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get bank accounts from akun_bank table
|
|
||||||
Future<List<Map<String, dynamic>>> getBankAccounts() async {
|
|
||||||
try {
|
|
||||||
debugPrint('🔍 Fetching bank accounts from akun_bank table');
|
|
||||||
|
|
||||||
final response = await client
|
|
||||||
.from('akun_bank')
|
|
||||||
.select('*')
|
|
||||||
.order('nama_bank', ascending: true);
|
|
||||||
|
|
||||||
debugPrint('✅ Fetched ${response.length} bank accounts');
|
|
||||||
|
|
||||||
// Convert response to List<Map<String, dynamic>>
|
|
||||||
List<Map<String, dynamic>> accounts = List<Map<String, dynamic>>.from(response);
|
|
||||||
|
|
||||||
return accounts;
|
|
||||||
} catch (e) {
|
|
||||||
debugPrint('❌ Error fetching bank accounts: $e');
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get sewa_aset details with aset data
|
// Get sewa_aset details with aset data
|
||||||
Future<Map<String, dynamic>?> getSewaAsetWithAsetData(
|
Future<Map<String, dynamic>?> getSewaAsetWithAsetData(
|
||||||
@ -967,29 +945,6 @@ class AsetProvider extends GetxService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all photos for a paket using id_paket column
|
|
||||||
Future<List<dynamic>> getFotoPaket(String paketId) async {
|
|
||||||
try {
|
|
||||||
debugPrint('📷 Fetching all photos for paket ID: $paketId');
|
|
||||||
|
|
||||||
final response = await client
|
|
||||||
.from('foto_aset')
|
|
||||||
.select('*')
|
|
||||||
.eq('id_paket', paketId);
|
|
||||||
|
|
||||||
if (response.isEmpty) {
|
|
||||||
debugPrint('⚠️ No photos found for paket $paketId');
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
debugPrint('✅ Found ${response.length} photos for paket $paketId');
|
|
||||||
return response;
|
|
||||||
} catch (e) {
|
|
||||||
debugPrint('❌ Error fetching photos for paket $paketId: $e');
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get paket data with their associated satuan waktu sewa data
|
// Get paket data with their associated satuan waktu sewa data
|
||||||
Future<List<dynamic>> getPakets() async {
|
Future<List<dynamic>> getPakets() async {
|
||||||
@ -1058,8 +1013,7 @@ class AsetProvider extends GetxService {
|
|||||||
|
|
||||||
// Default to hourly if not specified
|
// Default to hourly if not specified
|
||||||
String satuanWaktu = 'jam';
|
String satuanWaktu = 'jam';
|
||||||
if (swsResponse != null &&
|
if (swsResponse['satuan_waktu'] != null &&
|
||||||
swsResponse['satuan_waktu'] != null &&
|
|
||||||
swsResponse['satuan_waktu']['nama'] != null) {
|
swsResponse['satuan_waktu']['nama'] != null) {
|
||||||
satuanWaktu = swsResponse['satuan_waktu']['nama'];
|
satuanWaktu = swsResponse['satuan_waktu']['nama'];
|
||||||
}
|
}
|
||||||
@ -1087,7 +1041,7 @@ class AsetProvider extends GetxService {
|
|||||||
|
|
||||||
final response = await client.from('sewa_paket').insert(sewa).select();
|
final response = await client.from('sewa_paket').insert(sewa).select();
|
||||||
|
|
||||||
if (response != null && response.isNotEmpty) {
|
if (response.isNotEmpty) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -1096,4 +1050,189 @@ class AsetProvider extends GetxService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get photos for a package
|
||||||
|
Future<List<String>> getFotoPaket(String paketId) async {
|
||||||
|
try {
|
||||||
|
final response = await client
|
||||||
|
.from('foto_aset')
|
||||||
|
.select('foto_aset')
|
||||||
|
.eq('id_paket', paketId)
|
||||||
|
.order('created_at');
|
||||||
|
|
||||||
|
if (response != null && response.isNotEmpty) {
|
||||||
|
return response.map<String>((item) => item['foto_aset'] as String).toList();
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Error getting package photos: $e');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get items included in a package with additional asset details
|
||||||
|
Future<List<Map<String, dynamic>>> getPaketItems(String paketId) async {
|
||||||
|
debugPrint('🔄 [1/3] Starting to fetch items for paket ID: $paketId');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. First, get the basic package items (aset_id and kuantitas)
|
||||||
|
debugPrint('🔍 [2/3] Querying paket_item table for paket_id: $paketId');
|
||||||
|
|
||||||
|
final response = await client
|
||||||
|
.from('paket_item')
|
||||||
|
.select('''
|
||||||
|
aset_id,
|
||||||
|
kuantitas
|
||||||
|
''')
|
||||||
|
.eq('paket_id', paketId);
|
||||||
|
|
||||||
|
debugPrint('📊 Raw response from paket_item query:');
|
||||||
|
debugPrint(response.toString());
|
||||||
|
|
||||||
|
if (response == null) {
|
||||||
|
debugPrint('❌ [ERROR] Null response from paket_item query');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.isEmpty) {
|
||||||
|
debugPrint('ℹ️ [INFO] No items found in paket_item for paket ID: $paketId');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
debugPrint('✅ [SUCCESS] Found ${response.length} items in paket_item');
|
||||||
|
|
||||||
|
final List<Map<String, dynamic>> enrichedItems = [];
|
||||||
|
|
||||||
|
// Process each item to fetch additional details
|
||||||
|
debugPrint('🔄 [3/3] Processing ${response.length} items to fetch asset details');
|
||||||
|
|
||||||
|
for (var item in response) {
|
||||||
|
final String? asetId = item['aset_id']?.toString();
|
||||||
|
final int kuantitas = item['kuantitas'] ?? 1;
|
||||||
|
|
||||||
|
debugPrint('\n🔍 Processing item:');
|
||||||
|
debugPrint(' - Raw item data: $item');
|
||||||
|
debugPrint(' - aset_id: $asetId');
|
||||||
|
debugPrint(' - kuantitas: $kuantitas');
|
||||||
|
|
||||||
|
if (asetId == null || asetId.isEmpty) {
|
||||||
|
debugPrint('⚠️ [WARNING] Skipping item with null/empty aset_id');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. Get asset name from aset table
|
||||||
|
debugPrint(' - Querying aset table for id: $asetId');
|
||||||
|
final asetResponse = await client
|
||||||
|
.from('aset')
|
||||||
|
.select('id, nama, deskripsi')
|
||||||
|
.eq('id', asetId)
|
||||||
|
.maybeSingle();
|
||||||
|
|
||||||
|
debugPrint(' - Aset response: ${asetResponse?.toString() ?? 'null'}');
|
||||||
|
|
||||||
|
if (asetResponse == null) {
|
||||||
|
debugPrint('⚠️ [WARNING] No asset found with id: $asetId');
|
||||||
|
enrichedItems.add({
|
||||||
|
'aset_id': asetId,
|
||||||
|
'kuantitas': kuantitas,
|
||||||
|
'nama_aset': 'Item tidak diketahui',
|
||||||
|
'foto_aset': '',
|
||||||
|
'semua_foto': <String>[],
|
||||||
|
'error': 'Asset not found'
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Get only the first photo from foto_aset table
|
||||||
|
debugPrint(' - Querying first photo for id_aset: $asetId');
|
||||||
|
final fotoResponse = await client
|
||||||
|
.from('foto_aset')
|
||||||
|
.select('foto_aset')
|
||||||
|
.eq('id_aset', asetId)
|
||||||
|
.order('created_at', ascending: true)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
String? fotoUtama = '';
|
||||||
|
List<String> semuaFoto = [];
|
||||||
|
|
||||||
|
if (fotoResponse.isNotEmpty) {
|
||||||
|
final firstFoto = fotoResponse.first['foto_aset']?.toString();
|
||||||
|
if (firstFoto != null && firstFoto.isNotEmpty) {
|
||||||
|
fotoUtama = firstFoto;
|
||||||
|
semuaFoto = [firstFoto];
|
||||||
|
debugPrint(' - Found photo: $firstFoto');
|
||||||
|
} else {
|
||||||
|
debugPrint(' - No valid photo URL found');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
debugPrint(' - No photos found for asset $asetId');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Combine all data
|
||||||
|
final enrichedItem = {
|
||||||
|
'aset_id': asetId,
|
||||||
|
'kuantitas': kuantitas,
|
||||||
|
'nama_aset': asetResponse['nama']?.toString() ?? 'Nama tidak tersedia',
|
||||||
|
'foto_aset': fotoUtama,
|
||||||
|
'semua_foto': semuaFoto,
|
||||||
|
'debug': {
|
||||||
|
'aset_query': asetResponse,
|
||||||
|
'foto_count': semuaFoto.length
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
enrichedItems.add(enrichedItem);
|
||||||
|
|
||||||
|
// Debug log
|
||||||
|
debugPrint('✅ Successfully processed item:');
|
||||||
|
debugPrint(' - Aset ID: $asetId');
|
||||||
|
debugPrint(' - Nama: ${enrichedItem['nama_aset']}');
|
||||||
|
debugPrint(' - Kuantitas: $kuantitas');
|
||||||
|
debugPrint(' - Jumlah Foto: ${semuaFoto.length}');
|
||||||
|
if (semuaFoto.isNotEmpty) {
|
||||||
|
debugPrint(' - Foto Utama: ${semuaFoto.first}');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('❌ Error processing asset $asetId: $e');
|
||||||
|
// Still add the basic item even if we couldn't fetch additional details
|
||||||
|
enrichedItems.add({
|
||||||
|
'aset_id': asetId,
|
||||||
|
'kuantitas': item['kuantitas'],
|
||||||
|
'nama_aset': 'Nama Aset Tidak Ditemukan',
|
||||||
|
'foto_aset': '',
|
||||||
|
'semua_foto': <String>[],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debugPrint('✅ Successfully fetched ${enrichedItems.length} items with details');
|
||||||
|
return enrichedItems;
|
||||||
|
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
debugPrint('❌ Error getting package items for paket $paketId: $e');
|
||||||
|
debugPrint('Stack trace: $stackTrace');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get available bank accounts for payment
|
||||||
|
Future<List<Map<String, dynamic>>> getBankAccounts() async {
|
||||||
|
try {
|
||||||
|
final response = await client
|
||||||
|
.from('bank_accounts')
|
||||||
|
.select('*')
|
||||||
|
.eq('is_active', true)
|
||||||
|
.order('bank_name');
|
||||||
|
|
||||||
|
if (response != null && response.isNotEmpty) {
|
||||||
|
return List<Map<String, dynamic>>.from(response);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Error getting bank accounts: $e');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import '../controllers/order_sewa_paket_controller.dart';
|
import '../controllers/order_sewa_paket_controller.dart';
|
||||||
import '../../../data/providers/aset_provider.dart';
|
import '../../../data/providers/aset_provider.dart';
|
||||||
import '../../../data/providers/sewa_provider.dart';
|
|
||||||
|
|
||||||
class OrderSewaPaketBinding extends Bindings {
|
class OrderSewaPaketBinding extends Bindings {
|
||||||
@override
|
@override
|
||||||
@ -11,10 +10,6 @@ class OrderSewaPaketBinding extends Bindings {
|
|||||||
Get.put(AsetProvider());
|
Get.put(AsetProvider());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Get.isRegistered<SewaProvider>()) {
|
|
||||||
Get.put(SewaProvider());
|
|
||||||
}
|
|
||||||
|
|
||||||
Get.lazyPut<OrderSewaPaketController>(
|
Get.lazyPut<OrderSewaPaketController>(
|
||||||
() => OrderSewaPaketController(),
|
() => OrderSewaPaketController(),
|
||||||
);
|
);
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -392,7 +392,9 @@ class SewaAsetView extends GetView<SewaAsetController> {
|
|||||||
String imageUrl = paket['gambar_url'] ?? '';
|
String imageUrl = paket['gambar_url'] ?? '';
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () => _showPaketDetailModal(paket),
|
onTap: () {
|
||||||
|
_showPaketDetailModal(paket);
|
||||||
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppColors.surface,
|
color: AppColors.surface,
|
||||||
@ -535,7 +537,7 @@ class SewaAsetView extends GetView<SewaAsetController> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}).toList(),
|
}),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -571,7 +573,18 @@ class SewaAsetView extends GetView<SewaAsetController> {
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: () => _showPaketDetailModal(paket),
|
onPressed: () {
|
||||||
|
// Navigate directly to order page with package data
|
||||||
|
Get.toNamed(
|
||||||
|
Routes.ORDER_SEWA_PAKET,
|
||||||
|
arguments: {
|
||||||
|
'id': paket['id'],
|
||||||
|
'paketId': paket['id'],
|
||||||
|
'paketData': paket,
|
||||||
|
'satuanWaktuSewa': satuanWaktuSewa,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: AppColors.primary,
|
backgroundColor: AppColors.primary,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
@ -893,35 +906,37 @@ class SewaAsetView extends GetView<SewaAsetController> {
|
|||||||
|
|
||||||
// Order button
|
// Order button
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 8),
|
padding: const EdgeInsets.only(top: 16.0, bottom: 24.0),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 50,
|
height: 50,
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (satuanWaktuSewa.isEmpty) {
|
// Close the modal
|
||||||
Get.snackbar(
|
Get.back();
|
||||||
'Tidak Dapat Memesan',
|
// Navigate to order_sewa_paket page with package data
|
||||||
'Pilihan harga belum tersedia untuk paket ini',
|
Get.toNamed(
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
Routes.ORDER_SEWA_PAKET,
|
||||||
backgroundColor: Colors.red[100],
|
arguments: {
|
||||||
colorText: Colors.red[800],
|
'paket': paket,
|
||||||
);
|
'satuanWaktuSewa': satuanWaktuSewa,
|
||||||
return;
|
},
|
||||||
}
|
);
|
||||||
|
|
||||||
_showOrderPaketForm(paket, satuanWaktuSewa);
|
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
foregroundColor: Colors.white,
|
|
||||||
backgroundColor: AppColors.primary,
|
backgroundColor: AppColors.primary,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
|
elevation: 2,
|
||||||
),
|
),
|
||||||
child: const Text(
|
child: const Text(
|
||||||
'Pesan Paket Ini',
|
'Pesan Sekarang',
|
||||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -929,6 +944,9 @@ class SewaAsetView extends GetView<SewaAsetController> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
isScrollControlled: true,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
barrierColor: Colors.black54,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -945,10 +963,11 @@ class SewaAsetView extends GetView<SewaAsetController> {
|
|||||||
final RxInt duration = RxInt(selectedSWS.value?['durasi_min'] ?? 1);
|
final RxInt duration = RxInt(selectedSWS.value?['durasi_min'] ?? 1);
|
||||||
|
|
||||||
// Calculate total price
|
// Calculate total price
|
||||||
final calculateTotal = () {
|
calculateTotal() {
|
||||||
if (selectedSWS.value == null) return 0;
|
if (selectedSWS.value == null) return 0;
|
||||||
return (selectedSWS.value!['harga'] ?? 0) * duration.value;
|
return (selectedSWS.value!['harga'] ?? 0) * duration.value;
|
||||||
};
|
}
|
||||||
|
|
||||||
final RxInt totalPrice = RxInt(calculateTotal());
|
final RxInt totalPrice = RxInt(calculateTotal());
|
||||||
|
|
||||||
// Update total when duration or pricing option changes
|
// Update total when duration or pricing option changes
|
||||||
@ -1231,7 +1250,7 @@ class SewaAsetView extends GetView<SewaAsetController> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'Minimum ${minDuration} ${namaSatuanWaktu.toLowerCase()}',
|
'Minimum $minDuration ${namaSatuanWaktu.toLowerCase()}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: Colors.grey[600],
|
color: Colors.grey[600],
|
||||||
@ -1285,20 +1304,12 @@ class SewaAsetView extends GetView<SewaAsetController> {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
Get.back(); // Close the form
|
Get.back(); // Close the form
|
||||||
|
|
||||||
// Navigate to order_sewa_paket page
|
// Order the package
|
||||||
// Get the navigation service from the controller
|
controller.placeOrderPaket(
|
||||||
final navigationService = controller.navigationService;
|
paketId: paket['id'],
|
||||||
|
satuanWaktuSewaId: selectedSWS.value?['id'] ?? '',
|
||||||
// Store the selected parameters in a controller or pass as arguments
|
durasi: duration.value,
|
||||||
Get.toNamed(
|
totalHarga: totalPrice.value,
|
||||||
Routes.ORDER_SEWA_PAKET,
|
|
||||||
arguments: {
|
|
||||||
'paketId': paket['id'],
|
|
||||||
'satuanWaktuSewaId': selectedSWS.value?['id'] ?? '',
|
|
||||||
'durasi': duration.value,
|
|
||||||
'totalHarga': totalPrice.value,
|
|
||||||
'paketData': paket,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
|
@ -28,7 +28,7 @@ class NavigationService extends GetxService {
|
|||||||
Get.toNamed(Routes.SEWA_ASET, preventDuplicates: false);
|
Get.toNamed(Routes.SEWA_ASET, preventDuplicates: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Navigasi ke halaman Detail Sewa Aset dengan ID
|
/// Navigasi ke halaman Order Sewa Aset dengan ID
|
||||||
Future<void> toOrderSewaAset(String asetId) async {
|
Future<void> toOrderSewaAset(String asetId) async {
|
||||||
debugPrint('🧭 Navigating to OrderSewaAset with ID: $asetId');
|
debugPrint('🧭 Navigating to OrderSewaAset with ID: $asetId');
|
||||||
if (asetId.isEmpty) {
|
if (asetId.isEmpty) {
|
||||||
@ -50,6 +50,33 @@ class NavigationService extends GetxService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Navigasi ke halaman Pembayaran Sewa dengan ID sewa
|
||||||
|
Future<void> navigateToPembayaranSewa(String sewaId) async {
|
||||||
|
debugPrint('🧭 Navigating to PembayaranSewa with ID: $sewaId');
|
||||||
|
if (sewaId.isEmpty) {
|
||||||
|
Get.snackbar(
|
||||||
|
'Error',
|
||||||
|
'ID sewa tidak valid',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navigasi dengan arguments
|
||||||
|
Get.offAndToNamed(
|
||||||
|
Routes.PEMBAYARAN_SEWA,
|
||||||
|
arguments: {'sewaId': sewaId},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Kembali ke halaman Sewa Aset
|
||||||
|
void navigateToSewaAset() {
|
||||||
|
debugPrint('🧭 Navigating back to SewaAset');
|
||||||
|
Get.offAllNamed(Routes.SEWA_ASET);
|
||||||
|
}
|
||||||
|
|
||||||
/// Navigasi ke halaman Order Sewa Paket dengan ID
|
/// Navigasi ke halaman Order Sewa Paket dengan ID
|
||||||
Future<void> toOrderSewaPaket(String paketId) async {
|
Future<void> toOrderSewaPaket(String paketId) async {
|
||||||
debugPrint('🧭 Navigating to OrderSewaPaket with ID: $paketId');
|
debugPrint('🧭 Navigating to OrderSewaPaket with ID: $paketId');
|
||||||
|
86
lib/utils/constants/app_colors.dart
Normal file
86
lib/utils/constants/app_colors.dart
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class AppColors {
|
||||||
|
// Primary Colors
|
||||||
|
static const Color primary = Color(0xFF007AFF);
|
||||||
|
static const Color primaryDark = Color(0xFF0062CC);
|
||||||
|
static const Color primaryLight = Color(0xFF4DA6FF);
|
||||||
|
static const Color primarySoft = Color(0xFFE3F2FD); // Light blue, 10% opacity of primary
|
||||||
|
|
||||||
|
// Secondary Colors
|
||||||
|
static const Color secondary = Color(0xFF34C759);
|
||||||
|
static const Color secondaryDark = Color(0xFF248A3D);
|
||||||
|
static const Color secondaryLight = Color(0xFF5DDC7F);
|
||||||
|
|
||||||
|
// Status Colors
|
||||||
|
static const Color success = Color(0xFF34C759);
|
||||||
|
static const Color warning = Color(0xFFFF9500);
|
||||||
|
static const Color error = Color(0xFFFF3B30);
|
||||||
|
static const Color info = Color(0xFF007AFF);
|
||||||
|
|
||||||
|
// Grayscale
|
||||||
|
static const Color black = Color(0xFF000000);
|
||||||
|
static const Color gray1 = Color(0xFF1C1C1E);
|
||||||
|
static const Color gray2 = Color(0xFF2C2C2E);
|
||||||
|
static const Color gray3 = Color(0xFF48484A);
|
||||||
|
static const Color gray4 = Color(0xFF8E8E93);
|
||||||
|
static const Color gray5 = Color(0xFFAEAEB2);
|
||||||
|
static const Color gray6 = Color(0xFFC7C7CC);
|
||||||
|
static const Color gray7 = Color(0xFFD1D1D6);
|
||||||
|
static const Color gray8 = Color(0xFFE5E5EA);
|
||||||
|
static const Color gray9 = Color(0xFFF2F2F7);
|
||||||
|
static const Color white = Color(0xFFFFFFFF);
|
||||||
|
|
||||||
|
// UI Elements
|
||||||
|
static const Color border = Color(0xFFE5E5EA);
|
||||||
|
static const Color borderDark = Color(0xFFD1D1D6);
|
||||||
|
static const Color borderLight = Color(0xFFF0F0F0);
|
||||||
|
static const Color background = Color(0xFFFFFFFF);
|
||||||
|
static const Color cardBackground = Color(0xFFFFFFFF);
|
||||||
|
static const Color disabled = Color(0xFFD1D1D6);
|
||||||
|
static const Color placeholder = Color(0xFFC7C7CC);
|
||||||
|
|
||||||
|
// Surface Colors (for cards, sheets, menus, etc.)
|
||||||
|
static const Color surface = Color(0xFFFFFFFF);
|
||||||
|
static const Color surfaceLight = Color(0xFFF8F9FA); // Very light gray, slightly lighter than backgroundSecondary
|
||||||
|
|
||||||
|
// Text Colors
|
||||||
|
static const Color textPrimary = Color(0xFF000000);
|
||||||
|
static const Color textSecondary = Color(0xFF48484A);
|
||||||
|
static const Color textTertiary = Color(0xFF8E8E93);
|
||||||
|
static const Color textDisabled = Color(0xFFAEAEB2);
|
||||||
|
static const Color textInverse = Color(0xFFFFFFFF);
|
||||||
|
|
||||||
|
// Background Colors
|
||||||
|
static const Color backgroundPrimary = Color(0xFFFFFFFF);
|
||||||
|
static const Color backgroundSecondary = Color(0xFFF2F2F7);
|
||||||
|
static const Color backgroundTertiary = Color(0xFFE5E5EA);
|
||||||
|
|
||||||
|
// Overlay Colors
|
||||||
|
static const Color overlayDark = Color(0x99000000);
|
||||||
|
static const Color overlayLight = Color(0x33FFFFFF);
|
||||||
|
|
||||||
|
// Shadow Colors
|
||||||
|
static const Color shadow = Color(0x1A000000);
|
||||||
|
|
||||||
|
// Transparent
|
||||||
|
static const Color transparent = Color(0x00000000);
|
||||||
|
|
||||||
|
// Other UI Colors
|
||||||
|
static const Color divider = Color(0xFFE5E5EA);
|
||||||
|
static const Color highlight = Color(0x1A000000);
|
||||||
|
static const Color splash = Color(0x1A000000);
|
||||||
|
|
||||||
|
// Gradients
|
||||||
|
static const LinearGradient primaryGradient = LinearGradient(
|
||||||
|
begin: Alignment.topCenter,
|
||||||
|
end: Alignment.bottomCenter,
|
||||||
|
colors: [primary, primaryDark],
|
||||||
|
);
|
||||||
|
|
||||||
|
static const LinearGradient secondaryGradient = LinearGradient(
|
||||||
|
begin: Alignment.topCenter,
|
||||||
|
end: Alignment.bottomCenter,
|
||||||
|
colors: [secondary, secondaryDark],
|
||||||
|
);
|
||||||
|
}
|
Reference in New Issue
Block a user