Files
lis/mylis/lib/widgets/common_widgets.dart
T
2026-07-22 09:07:43 +07:00

170 lines
4.7 KiB
Dart

part of '../app/app.dart';
class DetailRow extends StatelessWidget {
const DetailRow({super.key, required this.label, required this.value});
final String label;
final Object? value;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: const TextStyle(color: Colors.black54, fontSize: 12),
),
const SizedBox(height: 2),
Text(
value?.toString().isNotEmpty == true ? value.toString() : '-',
style: const TextStyle(fontWeight: FontWeight.w600),
),
],
),
);
}
}
class StatusPill extends StatelessWidget {
const StatusPill({super.key, required this.status});
final String status;
@override
Widget build(BuildContext context) {
final lower = status.toLowerCase();
final color = lower.contains('selesai')
? const Color(0xFF15803D)
: lower.contains('dibatalkan')
? const Color(0xFF6B7280)
: lower.contains('warning') || lower.contains('target')
? const Color(0xFFDC2626)
: const Color(0xFF0F766E);
return BadgeLabel(text: status, color: color);
}
}
class BadgeLabel extends StatelessWidget {
const BadgeLabel({super.key, required this.text, required this.color});
final String text;
final Color color;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(6),
),
child: Text(
text,
style: TextStyle(
color: color,
fontWeight: FontWeight.w700,
fontSize: 12,
),
),
);
}
}
class EmptyPanel extends StatelessWidget {
const EmptyPanel({super.key, required this.text});
final String text;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 24),
child: Center(
child: Text(text, style: const TextStyle(color: Colors.black54)),
),
);
}
}
class ErrorView extends StatelessWidget {
const ErrorView({super.key, required this.message, required this.onRetry});
final String message;
final VoidCallback onRetry;
@override
Widget build(BuildContext context) {
return Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.error_outline, size: 42, color: Color(0xFFDC2626)),
const SizedBox(height: 10),
Text(message, textAlign: TextAlign.center),
const SizedBox(height: 12),
OutlinedButton.icon(
onPressed: onRetry,
icon: const Icon(Icons.refresh),
label: const Text('Coba Lagi'),
),
],
),
),
);
}
}
class LoadingScreen extends StatelessWidget {
const LoadingScreen({super.key});
@override
Widget build(BuildContext context) {
return const Scaffold(body: Center(child: CircularProgressIndicator()));
}
}
Map<String, dynamic> asMap(Object? value) {
if (value is Map<String, dynamic>) {
return value;
}
if (value is Map) {
return value.map((key, item) => MapEntry(key.toString(), item));
}
return <String, dynamic>{};
}
List<dynamic> asList(Object? value) => value is List ? value : <dynamic>[];
String _message(Object? error) => error is ApiException
? error.message
: 'Terjadi kesalahan saat memuat data.';
String decodeHtmlEntities(String value) {
var result = value;
for (var i = 0; i < 3; i += 1) {
final previous = result;
result = result
.replaceAll(RegExp(r'<\s*br\s*/?\s*>', caseSensitive: false), '\n')
.replaceAll(RegExp(r'</\s*p\s*>', caseSensitive: false), '\n')
.replaceAll(RegExp(r'<[^>]*>'), '')
.replaceAll('&gt;', '>')
.replaceAll('&lt;', '<')
.replaceAll('&amp;', '&')
.replaceAll('&quot;', '"')
.replaceAll('&#039;', "'")
.replaceAll('&apos;', "'");
result = result.replaceAllMapped(RegExp(r'&#(\d+);'), (match) {
final code = int.tryParse(match.group(1) ?? '');
return code == null ? match.group(0)! : String.fromCharCode(code);
});
result = result.replaceAllMapped(RegExp(r'&#x([0-9a-fA-F]+);'), (match) {
final code = int.tryParse(match.group(1) ?? '', radix: 16);
return code == null ? match.group(0)! : String.fromCharCode(code);
});
if (result == previous) {
break;
}
}
return result;
}