Perbarui struktur dan referensi file di dashboard_view.dart dan detail_donatur_view.dart. Tambahkan dokumentasi pada kelas DateTimeHelper dan perkenalan fungsi baru untuk format tanggal relatif serta nama hari dan bulan. Hapus widget yang tidak digunakan seperti detail_penitipan_dialog.dart, loading_indicator.dart, navigation_button.dart, statistic_card.dart, dan status_pill.dart untuk menyederhanakan kode.

This commit is contained in:
Khafidh Fuadi
2025-03-16 16:30:23 +07:00
parent 5814b19546
commit 078d74aad3
22 changed files with 1639 additions and 509 deletions

View File

@ -0,0 +1,164 @@
import 'package:flutter/material.dart';
import 'package:penyaluran_app/app/theme/app_colors.dart';
/// Item untuk dropdown
class DropdownItem<T> {
/// Nilai item
final T value;
/// Label yang ditampilkan
final String label;
/// Konstruktor untuk DropdownItem
const DropdownItem({
required this.value,
required this.label,
});
}
/// Input dropdown yang digunakan di seluruh aplikasi
///
/// Input ini dapat dikonfigurasi untuk berbagai jenis dropdown dan validasi.
class DropdownInput<T> extends StatelessWidget {
/// Label yang ditampilkan di atas input
final String label;
/// Hint yang ditampilkan di dalam input
final String? hint;
/// Daftar item dropdown
final List<DropdownItem<T>> items;
/// Nilai yang dipilih
final T? value;
/// Fungsi yang dipanggil ketika nilai dropdown berubah
final Function(T?)? onChanged;
/// Apakah input dinonaktifkan
final bool enabled;
/// Apakah input bersifat wajib
final bool required;
/// Pesan kesalahan yang ditampilkan di bawah input
final String? errorText;
/// Fungsi validasi input
final String? Function(T?)? validator;
/// Konstruktor untuk DropdownInput
const DropdownInput({
super.key,
required this.label,
this.hint,
required this.items,
this.value,
this.onChanged,
this.enabled = true,
this.required = false,
this.errorText,
this.validator,
});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Label
Row(
children: [
Text(
label,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppColors.textPrimary,
),
),
if (required)
Text(
' *',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppColors.error,
),
),
],
),
const SizedBox(height: 8),
// Dropdown
DropdownButtonFormField<T>(
value: value,
onChanged: enabled ? onChanged : null,
validator: validator,
isExpanded: true,
icon: const Icon(Icons.arrow_drop_down),
style: TextStyle(
fontSize: 14,
color: enabled ? AppColors.textPrimary : AppColors.textSecondary,
),
decoration: InputDecoration(
hintText: hint,
errorText: errorText,
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: AppColors.divider,
width: 1,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: AppColors.divider,
width: 1,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: AppColors.primary,
width: 1.5,
),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: AppColors.error,
width: 1,
),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: AppColors.error,
width: 1.5,
),
),
disabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: AppColors.disabled,
width: 1,
),
),
),
items: items.map((DropdownItem<T> item) {
return DropdownMenuItem<T>(
value: item.value,
child: Text(item.label),
);
}).toList(),
),
],
);
}
}

View File

@ -0,0 +1,193 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:penyaluran_app/app/theme/app_colors.dart';
/// Input teks yang digunakan di seluruh aplikasi
///
/// Input ini dapat dikonfigurasi untuk berbagai jenis input dan validasi.
class TextInput extends StatelessWidget {
/// Label yang ditampilkan di atas input
final String label;
/// Hint yang ditampilkan di dalam input
final String? hint;
/// Controller untuk input
final TextEditingController? controller;
/// Fungsi yang dipanggil ketika nilai input berubah
final Function(String)? onChanged;
/// Fungsi yang dipanggil ketika input selesai diedit
final Function(String)? onSubmitted;
/// Apakah input dinonaktifkan
final bool enabled;
/// Apakah input bersifat wajib
final bool required;
/// Apakah input bersifat rahasia (password)
final bool obscureText;
/// Pesan kesalahan yang ditampilkan di bawah input
final String? errorText;
/// Ikon yang ditampilkan di sebelah kanan input
final IconData? suffixIcon;
/// Fungsi yang dipanggil ketika ikon di sebelah kanan input ditekan
final VoidCallback? onSuffixIconPressed;
/// Jenis keyboard yang digunakan
final TextInputType keyboardType;
/// Daftar pemformatan input
final List<TextInputFormatter>? inputFormatters;
/// Jumlah baris input (untuk input multiline)
final int? maxLines;
/// Jumlah karakter maksimum
final int? maxLength;
/// Apakah input otomatis mendapatkan fokus
final bool autofocus;
/// Fokus node untuk input
final FocusNode? focusNode;
/// Fungsi validasi input
final String? Function(String?)? validator;
/// Konstruktor untuk TextInput
const TextInput({
super.key,
required this.label,
this.hint,
this.controller,
this.onChanged,
this.onSubmitted,
this.enabled = true,
this.required = false,
this.obscureText = false,
this.errorText,
this.suffixIcon,
this.onSuffixIconPressed,
this.keyboardType = TextInputType.text,
this.inputFormatters,
this.maxLines = 1,
this.maxLength,
this.autofocus = false,
this.focusNode,
this.validator,
});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Label
Row(
children: [
Text(
label,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppColors.textPrimary,
),
),
if (required)
Text(
' *',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppColors.error,
),
),
],
),
const SizedBox(height: 8),
// Input
TextFormField(
controller: controller,
onChanged: onChanged,
onFieldSubmitted: onSubmitted,
enabled: enabled,
obscureText: obscureText,
keyboardType: keyboardType,
inputFormatters: inputFormatters,
maxLines: maxLines,
maxLength: maxLength,
autofocus: autofocus,
focusNode: focusNode,
validator: validator,
style: TextStyle(
fontSize: 14,
color: enabled ? AppColors.textPrimary : AppColors.textSecondary,
),
decoration: InputDecoration(
hintText: hint,
errorText: errorText,
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: AppColors.divider,
width: 1,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: AppColors.divider,
width: 1,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: AppColors.primary,
width: 1.5,
),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: AppColors.error,
width: 1,
),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: AppColors.error,
width: 1.5,
),
),
disabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: AppColors.disabled,
width: 1,
),
),
suffixIcon: suffixIcon != null
? IconButton(
icon: Icon(suffixIcon),
onPressed: onSuffixIconPressed,
)
: null,
),
),
],
);
}
}