Add pricing entry flow and platform admin foundations
This commit is contained in:
@@ -9,7 +9,10 @@ import '../../../core/providers/locale_provider.dart';
|
||||
import '../../../core/router/app_router.dart';
|
||||
import '../../../core/theme/app_theme.dart';
|
||||
import '../../../models/tenant.dart';
|
||||
import '../../shared/location_completion_banner.dart';
|
||||
import '../../shared/tenant_team_screen.dart';
|
||||
import '../../shared/location_picker_sheet.dart';
|
||||
import '../../shared/tenant_location_data.dart';
|
||||
import '../connections/clinic_connections_screen.dart';
|
||||
|
||||
class ClinicSettingsScreen extends ConsumerWidget {
|
||||
@@ -29,6 +32,17 @@ class ClinicSettingsScreen extends ConsumerWidget {
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
if (tenant?.hasLocation != true) ...[
|
||||
LocationCompletionBanner(
|
||||
title: 'Konum eksik',
|
||||
description:
|
||||
'Kliniğiniz harita tabanlı aramalarda doğru eşleşme için koordinat bilgisine ihtiyaç duyuyor.',
|
||||
buttonLabel: 'Konumu Düzenle',
|
||||
onTap: () => _showEditSheet(context, ref, tenant, s),
|
||||
compact: true,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
// User card
|
||||
_SectionHeader(title: s.userInfo),
|
||||
_UserCard(profile: profile),
|
||||
@@ -62,6 +76,13 @@ class ClinicSettingsScreen extends ConsumerWidget {
|
||||
label: s.role,
|
||||
value: _roleLabel(membership?.role, s),
|
||||
),
|
||||
_InfoTile(
|
||||
icon: Icons.place_outlined,
|
||||
label: 'Konum',
|
||||
value: tenant?.locationLabel.isNotEmpty == true
|
||||
? tenant!.locationLabel
|
||||
: '-',
|
||||
),
|
||||
]),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
@@ -100,7 +121,9 @@ class ClinicSettingsScreen extends ConsumerWidget {
|
||||
onTap: () {
|
||||
ref.read(authProvider.notifier).setActiveTenant(m);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(s.tenantSelected(m.tenant.companyName))),
|
||||
SnackBar(
|
||||
content:
|
||||
Text(s.tenantSelected(m.tenant.companyName))),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -120,8 +143,7 @@ class ClinicSettingsScreen extends ConsumerWidget {
|
||||
subtitle: s.teamSub,
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => const TenantTeamScreen()),
|
||||
MaterialPageRoute(builder: (_) => const TenantTeamScreen()),
|
||||
),
|
||||
),
|
||||
_NavTile(
|
||||
@@ -140,6 +162,14 @@ class ClinicSettingsScreen extends ConsumerWidget {
|
||||
subtitle: s.aiAssistantSub,
|
||||
onTap: () => context.push(routeClinicAi),
|
||||
),
|
||||
_NavTile(
|
||||
icon: Icons.workspace_premium_outlined,
|
||||
iconColor: AppColors.primary,
|
||||
iconBg: const Color(0xFFEFF6FF),
|
||||
title: 'Paketler ve AI Kredileri',
|
||||
subtitle: 'Trial ve paket görünümünü incele',
|
||||
onTap: () => context.push(routeWelcome),
|
||||
),
|
||||
]),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
@@ -152,7 +182,8 @@ class ClinicSettingsScreen extends ConsumerWidget {
|
||||
iconColor: AppColors.accent,
|
||||
iconBg: AppColors.inProgressBg,
|
||||
title: s.appLanguage,
|
||||
subtitle: _currentLanguageLabel(ref.watch(localeProvider).languageCode, s),
|
||||
subtitle: _currentLanguageLabel(
|
||||
ref.watch(localeProvider).languageCode, s),
|
||||
onTap: () => _showLanguagePicker(context, ref, s),
|
||||
),
|
||||
]),
|
||||
@@ -176,7 +207,8 @@ class ClinicSettingsScreen extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
void _showEditSheet(BuildContext context, WidgetRef ref, Tenant? tenant, AppStrings s) {
|
||||
void _showEditSheet(
|
||||
BuildContext context, WidgetRef ref, Tenant? tenant, AppStrings s) {
|
||||
if (tenant == null) return;
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
@@ -185,10 +217,12 @@ class ClinicSettingsScreen extends ConsumerWidget {
|
||||
builder: (_) => _EditTenantSheet(
|
||||
tenant: tenant,
|
||||
s: s,
|
||||
onSave: (name) async {
|
||||
await ref
|
||||
.read(authProvider.notifier)
|
||||
.updateTenantInfo(tenantId: tenant.id, companyName: name);
|
||||
onSave: (name, location) async {
|
||||
await ref.read(authProvider.notifier).updateTenantInfo(
|
||||
tenantId: tenant.id,
|
||||
companyName: name,
|
||||
location: location,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -202,7 +236,8 @@ class ClinicSettingsScreen extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
static String _currentLanguageLabel(String code, AppStrings s) => switch (code) {
|
||||
static String _currentLanguageLabel(String code, AppStrings s) =>
|
||||
switch (code) {
|
||||
'en' => s.languageEnglish,
|
||||
'ru' => s.languageRussian,
|
||||
'ar' => s.languageArabic,
|
||||
@@ -316,7 +351,10 @@ class _EditTenantSheet extends StatefulWidget {
|
||||
});
|
||||
final Tenant tenant;
|
||||
final AppStrings s;
|
||||
final Future<void> Function(String companyName) onSave;
|
||||
final Future<void> Function(
|
||||
String companyName,
|
||||
TenantLocationData location,
|
||||
) onSave;
|
||||
|
||||
@override
|
||||
State<_EditTenantSheet> createState() => _EditTenantSheetState();
|
||||
@@ -324,32 +362,49 @@ class _EditTenantSheet extends StatefulWidget {
|
||||
|
||||
class _EditTenantSheetState extends State<_EditTenantSheet> {
|
||||
late final TextEditingController _nameController;
|
||||
late final TextEditingController _addressController;
|
||||
late final TextEditingController _cityController;
|
||||
late final TextEditingController _districtController;
|
||||
late TenantLocationData _location;
|
||||
bool _saving = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_nameController = TextEditingController(text: widget.tenant.companyName);
|
||||
_location = TenantLocationData.fromTenant(widget.tenant);
|
||||
_addressController = TextEditingController(text: _location.address ?? '');
|
||||
_cityController = TextEditingController(text: _location.city ?? '');
|
||||
_districtController = TextEditingController(text: _location.district ?? '');
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_nameController.dispose();
|
||||
_addressController.dispose();
|
||||
_cityController.dispose();
|
||||
_districtController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _submit() async {
|
||||
final name = _nameController.text.trim();
|
||||
if (name.isEmpty) return;
|
||||
final location = _location.copyWith(
|
||||
address: _addressController.text.trim(),
|
||||
city: _cityController.text.trim(),
|
||||
district: _districtController.text.trim(),
|
||||
);
|
||||
if (!location.hasDetails) return;
|
||||
setState(() => _saving = true);
|
||||
final navigator = Navigator.of(context);
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
try {
|
||||
await widget.onSave(name);
|
||||
await widget.onSave(name, location);
|
||||
navigator.pop();
|
||||
} catch (e) {
|
||||
messenger.showSnackBar(
|
||||
SnackBar(content: Text('${widget.s.errorPrefix}: $e')));
|
||||
messenger
|
||||
.showSnackBar(SnackBar(content: Text('${widget.s.errorPrefix}: $e')));
|
||||
} finally {
|
||||
if (mounted) setState(() => _saving = false);
|
||||
}
|
||||
@@ -395,13 +450,91 @@ class _EditTenantSheetState extends State<_EditTenantSheet> {
|
||||
),
|
||||
textCapitalization: TextCapitalization.words,
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: AppColors.border),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Konum',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
_location.fullLabel.isNotEmpty
|
||||
? _location.fullLabel
|
||||
: 'Henüz konum veya adres bilgisi girilmedi.',
|
||||
style: const TextStyle(color: AppColors.textSecondary),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
OutlinedButton.icon(
|
||||
onPressed: () async {
|
||||
final picked = await showLocationPickerSheet(
|
||||
context,
|
||||
initialLocation: _location,
|
||||
title: 'Klinik Konumu',
|
||||
);
|
||||
if (picked != null) {
|
||||
setState(() => _location = picked);
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.map_outlined),
|
||||
label: const Text('Haritadan Konum Seç'),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextFormField(
|
||||
controller: _addressController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Açık Adres',
|
||||
hintText: 'Cadde, sokak, mahalle bilgisi',
|
||||
),
|
||||
maxLines: 2,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: _cityController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Şehir',
|
||||
),
|
||||
textCapitalization: TextCapitalization.words,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: _districtController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'İlçe',
|
||||
),
|
||||
textCapitalization: TextCapitalization.words,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
if (_saving)
|
||||
const Center(
|
||||
child: CircularProgressIndicator(color: AppColors.accent))
|
||||
else
|
||||
FilledButton(
|
||||
onPressed: _submit,
|
||||
onPressed: _saving ? null : _submit,
|
||||
style: FilledButton.styleFrom(
|
||||
minimumSize: const Size(double.infinity, 48)),
|
||||
child: Text(s.save),
|
||||
@@ -534,7 +667,10 @@ class _InfoCard extends StatelessWidget {
|
||||
children[i],
|
||||
if (i < children.length - 1)
|
||||
const Divider(
|
||||
height: 1, indent: 16, endIndent: 16, color: AppColors.border),
|
||||
height: 1,
|
||||
indent: 16,
|
||||
endIndent: 16,
|
||||
color: AppColors.border),
|
||||
],
|
||||
],
|
||||
),
|
||||
@@ -599,8 +735,7 @@ class _NavTile extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 2),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 2),
|
||||
leading: Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
@@ -615,8 +750,7 @@ class _NavTile extends StatelessWidget {
|
||||
? Text(subtitle!,
|
||||
style: const TextStyle(color: AppColors.textSecondary))
|
||||
: null,
|
||||
trailing:
|
||||
const Icon(Icons.chevron_right, color: AppColors.textSecondary),
|
||||
trailing: const Icon(Icons.chevron_right, color: AppColors.textSecondary),
|
||||
onTap: onTap,
|
||||
);
|
||||
}
|
||||
@@ -642,16 +776,14 @@ class _SignOutCard extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
child: ListTile(
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 2),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 2),
|
||||
leading: Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.cancelledBg,
|
||||
borderRadius: BorderRadius.circular(9)),
|
||||
child: const Icon(Icons.logout,
|
||||
color: AppColors.cancelled, size: 18),
|
||||
child: const Icon(Icons.logout, color: AppColors.cancelled, size: 18),
|
||||
),
|
||||
title: Text(s.signOut,
|
||||
style: const TextStyle(
|
||||
|
||||
Reference in New Issue
Block a user