Files
barber-app/lib/features/haircuts/presentation/pages/add_haircut_page.dart
2025-12-19 23:29:24 -03:00

361 lines
13 KiB
Dart

import 'package:flutter/material.dart';
import 'package:barber_app/core/theme/app_theme.dart';
import 'package:barber_app/core/constants/app_strings.dart';
import 'package:barber_app/features/haircuts/data/repositories/haircut_repository.dart';
import 'package:barber_app/features/services/data/models/service_model.dart';
import 'package:barber_app/features/services/data/repositories/service_repository.dart';
import 'package:barber_app/features/services/presentation/pages/services_page.dart';
import 'package:barber_app/shared/widgets/custom_text_field.dart';
import 'package:barber_app/shared/widgets/loading_button.dart';
import 'package:intl/intl.dart';
class AddHaircutPage extends StatefulWidget {
const AddHaircutPage({super.key});
@override
State<AddHaircutPage> createState() => _AddHaircutPageState();
}
class _AddHaircutPageState extends State<AddHaircutPage> {
final _formKey = GlobalKey<FormState>();
final _clientNameController = TextEditingController();
final _priceController = TextEditingController();
final _notesController = TextEditingController();
final _serviceRepo = ServiceRepository();
List<ServiceModel> _services = [];
ServiceModel? _selectedService;
bool _isLoading = false;
final _paymentMethods = [
'Dinheiro',
'PIX',
'Cartão de Crédito',
'Cartão de Débito',
'Fiado',
];
String _selectedPaymentMethod = 'Dinheiro';
final _currencyFormat = NumberFormat.currency(locale: 'pt_BR', symbol: 'R\$');
@override
void initState() {
super.initState();
_loadServices();
}
void _loadServices() {
setState(() {
_services = _serviceRepo.getAllServices();
});
}
void _onServiceSelected(ServiceModel? service) {
if (service == null) return;
setState(() {
_selectedService = service;
// Preenche o valor automaticamente
_priceController.text = service.price.toStringAsFixed(2);
});
}
Future<void> _saveHaircut() async {
if (!(_formKey.currentState?.validate() ?? false)) return;
// Validação flexível: Se selecionou serviço ou digitou preço
// Aqui assumimos que serviceType será o nome do serviço selecionado
// Se não selecionou, impedimos? Sim, melhor forçar seleção ou ter um 'Outro'
if (_selectedService == null) {
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('Selecione um serviço')));
return;
}
setState(() => _isLoading = true);
try {
final haircutRepo = HaircutRepository();
final price =
double.tryParse(_priceController.text.replaceAll(',', '.')) ??
_selectedService!.price;
await haircutRepo.createHaircut(
clientName: _clientNameController.text,
serviceType: _selectedService!.name,
price: price,
dateTime: DateTime.now(),
notes: _notesController.text.isNotEmpty ? _notesController.text : null,
isPaid: _selectedPaymentMethod != 'Fiado',
paymentMethod: _selectedPaymentMethod,
);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(AppStrings.savedSuccessfully),
backgroundColor: AppColors.success,
),
);
Navigator.pop(context);
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Erro: $e'), backgroundColor: AppColors.error),
);
}
} finally {
if (mounted) {
setState(() => _isLoading = false);
}
}
}
@override
void dispose() {
_clientNameController.dispose();
_priceController.dispose();
_notesController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(AppStrings.newHaircut),
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios),
onPressed: () => Navigator.pop(context),
),
),
body: SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Cliente
CustomTextField(
controller: _clientNameController,
label: AppStrings.clientName,
hint: 'Nome do cliente',
prefixIcon: Icons.person_outline,
textCapitalization: TextCapitalization.words,
validator: (v) =>
v?.isEmpty ?? true ? 'Informe o nome do cliente' : null,
),
const SizedBox(height: 24),
// Lista de Serviços (Chips)
const Text(
'Selecione o Serviço',
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
if (_services.isEmpty)
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.surfaceLight,
borderRadius: BorderRadius.circular(12),
),
child: Column(
children: [
const Text(
'Nenhum serviço cadastrado.',
style: TextStyle(color: AppColors.textSecondary),
),
const SizedBox(height: 12),
ElevatedButton.icon(
onPressed: () async {
await Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const ServicesPage(),
),
);
_loadServices();
},
icon: const Icon(Icons.add, size: 18),
label: const Text('Cadastrar Serviços'),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryColor,
foregroundColor: Colors.black,
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
),
),
],
),
)
else
Wrap(
spacing: 8,
runSpacing: 8,
children: _services.map((service) {
final isSelected = _selectedService?.id == service.id;
return ChoiceChip(
label: Text(
'${service.name} - ${_currencyFormat.format(service.price)}',
style: TextStyle(
color: isSelected
? Colors.black
: (Theme.of(context).brightness ==
Brightness.dark
? AppColors.textPrimary
: Colors.black87),
fontWeight: isSelected
? FontWeight.bold
: FontWeight.normal,
),
),
selected: isSelected,
selectedColor: AppColors.primaryColor,
backgroundColor: AppColors.surface,
side: BorderSide(
color: isSelected
? AppColors.primaryColor
: AppColors.surfaceLight,
),
onSelected: (selected) {
if (selected) _onServiceSelected(service);
},
);
}).toList(),
),
const SizedBox(height: 24),
// Valor (Editável)
CustomTextField(
controller: _priceController,
label: AppStrings.price,
hint: '0,00',
prefixIcon: Icons.attach_money,
keyboardType: TextInputType.number,
validator: (v) {
if (v?.isEmpty ?? true) return 'Informe o valor';
final parsed = double.tryParse(v!.replaceAll(',', '.'));
if (parsed == null || parsed < 0) return 'Valor inválido';
return null;
},
),
const SizedBox(height: 24),
// Observações
CustomTextField(
controller: _notesController,
label: AppStrings.notes,
hint: 'Observações (opcional)',
prefixIcon: Icons.note_outlined,
maxLines: 3,
),
const SizedBox(height: 16),
// Forma de Pagamento
Container(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 4,
),
decoration: BoxDecoration(
color: Theme.of(context).brightness == Brightness.dark
? AppColors.surface
: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: AppColors.surfaceLight),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(top: 8, bottom: 4),
child: Text(
'Forma de Pagamento',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color:
Theme.of(context).brightness == Brightness.dark
? AppColors.textSecondary
: Colors.black54,
),
),
),
DropdownButtonFormField<String>(
initialValue: _selectedPaymentMethod,
decoration: const InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.zero,
isDense: true,
),
dropdownColor:
Theme.of(context).brightness == Brightness.dark
? AppColors.surface
: Colors.white,
style: TextStyle(
color: Theme.of(context).brightness == Brightness.dark
? AppColors.textPrimary
: Colors.black87,
fontSize: 16,
),
onChanged: (String? newValue) {
if (newValue != null) {
setState(() {
_selectedPaymentMethod = newValue;
});
}
},
items: _paymentMethods.map<DropdownMenuItem<String>>((
String value,
) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
// Feedback visual do status
Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Text(
_selectedPaymentMethod == 'Fiado'
? 'Status: Pendente (A Receber)'
: 'Status: Pago (Entra no Caixa)',
style: TextStyle(
fontSize: 12,
color: _selectedPaymentMethod == 'Fiado'
? AppColors.warning
: AppColors.success,
),
),
),
],
),
),
const SizedBox(height: 32),
LoadingButton(
text: 'Salvar Corte',
isLoading: _isLoading,
onPressed: _saveHaircut,
),
],
),
),
),
),
);
}
}