Implemented insertion of new items into table
This commit is contained in:
parent
0c14da73df
commit
dfbea7cb6c
4 changed files with 1107 additions and 1 deletions
942
lib/pages/dialogs/open_table_dialog.dart
Normal file
942
lib/pages/dialogs/open_table_dialog.dart
Normal file
|
|
@ -0,0 +1,942 @@
|
|||
import 'package:data_table_2/data_table_2.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_fast_forms/flutter_fast_forms.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:omni_datetime_picker/omni_datetime_picker.dart';
|
||||
import 'package:recase/recase.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:tuuli_api/tuuli_api.dart';
|
||||
import 'package:tuuli_app/api_controller.dart';
|
||||
import 'package:tuuli_app/models/db_column_definition.dart';
|
||||
import 'package:tuuli_app/models/user_definition.dart';
|
||||
import 'package:tuuli_app/utils.dart';
|
||||
import 'package:tuuli_app/widgets/data_input_dialog.dart';
|
||||
|
||||
class OpenTableController extends GetxController {
|
||||
final TableDefinition table;
|
||||
|
||||
OpenTableController({required this.table});
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
|
||||
refreshTableData();
|
||||
}
|
||||
|
||||
final _userCache = <int, UserDefinition?>{}.obs;
|
||||
UserDefinition? getUserFromCache(int id) {
|
||||
return _userCache[id];
|
||||
}
|
||||
|
||||
void putUserInCache(UserDefinition user) {
|
||||
_userCache[user.id] = user;
|
||||
}
|
||||
|
||||
final _assetsCache = <int, Asset?>{}.obs;
|
||||
Asset? getAssetFromCache(int id) {
|
||||
return _assetsCache[id];
|
||||
}
|
||||
|
||||
void putAssetInCache(Asset asset) {
|
||||
_assetsCache[asset.id] = asset;
|
||||
}
|
||||
|
||||
final _tableData = <Map<String, dynamic>>[].obs;
|
||||
List<Map<String, dynamic>> get tableData => _tableData;
|
||||
|
||||
final _newRowData = <String, dynamic>{}.obs;
|
||||
Map<String, dynamic> get newRowData => _newRowData;
|
||||
void setNewRowData(String key, dynamic value) {
|
||||
_newRowData[key] = value;
|
||||
}
|
||||
|
||||
void clearNewRowData() {
|
||||
_newRowData.clear();
|
||||
}
|
||||
|
||||
Future<void> refreshTableData() async {
|
||||
try {
|
||||
final resp = await ApiController.to.apiClient.getItemsFromTable(
|
||||
tableName: table.tableName,
|
||||
itemsSelector: const ItemsSelector(
|
||||
fields: [
|
||||
"*",
|
||||
],
|
||||
where: [],
|
||||
),
|
||||
);
|
||||
|
||||
final respData = resp.data;
|
||||
if (respData == null) {
|
||||
throw Exception("No data in response");
|
||||
}
|
||||
|
||||
_tableData.clear();
|
||||
_tableData.addAll(respData);
|
||||
} on DioError catch (e) {
|
||||
final respData = e.response?.data;
|
||||
if (respData != null) {
|
||||
Get.snackbar(
|
||||
"Error trying to get table data",
|
||||
"${respData['error']}",
|
||||
);
|
||||
} else {
|
||||
Get.snackbar(
|
||||
"Error trying to get table data",
|
||||
"$e",
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
Get.snackbar(
|
||||
"Error trying to get table data",
|
||||
"$e",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<UserDefinition?> showUserPicker() async {
|
||||
final username = "".obs;
|
||||
|
||||
final user = await Get.dialog<UserDefinition>(
|
||||
AlertDialog(
|
||||
title: const Text("Select user"),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextField(
|
||||
onChanged: (value) {
|
||||
username.value = value;
|
||||
},
|
||||
decoration: const InputDecoration(
|
||||
labelText: "Username",
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => FutureBuilder<List<UserDefinition>>(
|
||||
future: () async {
|
||||
if (username.value.isEmpty) {
|
||||
return <UserDefinition>[];
|
||||
}
|
||||
|
||||
final resp =
|
||||
await ApiController.to.apiClient.getItemsFromTable(
|
||||
tableName: "users",
|
||||
itemsSelector: ItemsSelector(
|
||||
fields: [
|
||||
"id",
|
||||
"username",
|
||||
],
|
||||
where: [
|
||||
ColumnConditionCompat(
|
||||
column: "username",
|
||||
operator_:
|
||||
ColumnConditionCompatOperator.contains,
|
||||
value: username.value,
|
||||
)
|
||||
],
|
||||
));
|
||||
|
||||
final respData = resp.data;
|
||||
if (respData == null) {
|
||||
throw Exception("No data in response");
|
||||
}
|
||||
|
||||
return respData
|
||||
.map((e) => UserDefinition(
|
||||
id: e["id"],
|
||||
username: e["username"],
|
||||
password: "",
|
||||
accessToken: "",
|
||||
))
|
||||
.toList(growable: false);
|
||||
}(),
|
||||
initialData: const [],
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError) {
|
||||
return Text("${snapshot.error}");
|
||||
}
|
||||
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const CircularProgressIndicator();
|
||||
}
|
||||
|
||||
final users = snapshot.data!;
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
for (final user in users)
|
||||
ListTile(
|
||||
title: Text(user.username),
|
||||
onTap: () {
|
||||
Get.back(result: user);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Get.back(result: false);
|
||||
},
|
||||
child: const Text("Cancel"),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
Future<Asset?> showAssetPicker() async {
|
||||
final name = "".obs;
|
||||
|
||||
final asset = await Get.dialog<Asset>(
|
||||
AlertDialog(
|
||||
title: const Text("Select asset"),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextField(
|
||||
onChanged: (value) {
|
||||
name.value = value;
|
||||
},
|
||||
decoration: const InputDecoration(
|
||||
labelText: "Filename",
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => FutureBuilder<List<Asset>>(
|
||||
future: () async {
|
||||
if (name.value.isEmpty) {
|
||||
return <Asset>[];
|
||||
}
|
||||
|
||||
final resp =
|
||||
await ApiController.to.apiClient.getItemsFromTable(
|
||||
tableName: "assets",
|
||||
itemsSelector: ItemsSelector(
|
||||
fields: [
|
||||
"id",
|
||||
"name",
|
||||
"description",
|
||||
],
|
||||
where: [
|
||||
ColumnConditionCompat(
|
||||
column: "name",
|
||||
operator_:
|
||||
ColumnConditionCompatOperator.contains,
|
||||
value: name.value,
|
||||
)
|
||||
],
|
||||
));
|
||||
|
||||
final respData = resp.data;
|
||||
if (respData == null) {
|
||||
throw Exception("No data in response");
|
||||
}
|
||||
|
||||
return respData
|
||||
.map((e) => Asset(
|
||||
id: e["id"],
|
||||
name: e["name"],
|
||||
description: e["description"],
|
||||
fid: "",
|
||||
tags: "",
|
||||
))
|
||||
.toList(growable: false);
|
||||
}(),
|
||||
initialData: const [],
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError) {
|
||||
return Text("${snapshot.error}");
|
||||
}
|
||||
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const CircularProgressIndicator();
|
||||
}
|
||||
|
||||
final assets = snapshot.data!;
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
for (final asset in assets)
|
||||
ListTile(
|
||||
title: Text(asset.name),
|
||||
subtitle: Text(asset.description),
|
||||
onTap: () {
|
||||
Get.back(result: asset);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Get.back(result: null);
|
||||
},
|
||||
child: const Text("Cancel"),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
return asset;
|
||||
}
|
||||
|
||||
Future<void> addNewRow() async {
|
||||
try {
|
||||
final resp = await ApiController.to.apiClient.createItem(
|
||||
tableName: table.tableName,
|
||||
itemDefinition: convertToPayload(newRowData),
|
||||
);
|
||||
|
||||
final respData = resp.data;
|
||||
if (respData == null) {
|
||||
throw Exception("No data in response");
|
||||
}
|
||||
|
||||
clearNewRowData();
|
||||
refreshTableData();
|
||||
} on DioError catch (e) {
|
||||
final respData = e.response?.data;
|
||||
if (respData != null) {
|
||||
Get.snackbar(
|
||||
"Error trying to get table data",
|
||||
"${respData['error']}",
|
||||
);
|
||||
} else {
|
||||
Get.snackbar(
|
||||
"Error trying to get table data",
|
||||
"$e",
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
Get.snackbar(
|
||||
"Error trying to get table data",
|
||||
"$e",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateItem(
|
||||
Map<String, dynamic> originalItem,
|
||||
String columnName,
|
||||
dynamic data,
|
||||
) async {
|
||||
final idCol = table.parsedColumns
|
||||
.firstWhereOrNull((e) => e is PrimarySerialColumnDefinition);
|
||||
try {
|
||||
final resp = await ApiController.to.apiClient.updateItemInTable(
|
||||
tableName: table.tableName,
|
||||
itemUpdate: ItemUpdate(
|
||||
oldItem: idCol == null
|
||||
? convertToPayload(originalItem)
|
||||
: {
|
||||
idCol.name: originalItem[idCol.name],
|
||||
},
|
||||
item: convertToPayload({columnName: data}),
|
||||
),
|
||||
);
|
||||
|
||||
final respData = resp.data;
|
||||
if (respData == null) {
|
||||
throw Exception("No data in response");
|
||||
}
|
||||
|
||||
refreshTableData();
|
||||
} on DioError catch (e) {
|
||||
final respData = e.response?.data;
|
||||
if (respData != null) {
|
||||
Get.snackbar(
|
||||
"Error trying to update table data",
|
||||
"${respData['error']}",
|
||||
);
|
||||
} else {
|
||||
Get.snackbar(
|
||||
"Error trying to update table data",
|
||||
"$e",
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
Get.snackbar(
|
||||
"Error trying to update table data",
|
||||
"$e",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteItem(Map<String, dynamic> e) async {
|
||||
final idCol = table.parsedColumns
|
||||
.firstWhereOrNull((e) => e is PrimarySerialColumnDefinition);
|
||||
try {
|
||||
final resp = await ApiController.to.apiClient.deleteItemFromTable(
|
||||
tableName: table.tableName,
|
||||
columnConditionCompat: [
|
||||
if (idCol != null)
|
||||
ColumnConditionCompat(
|
||||
column: idCol.name,
|
||||
operator_: ColumnConditionCompatOperator.eq,
|
||||
value: e[idCol.name],
|
||||
)
|
||||
else
|
||||
...convertToPayload(e).entries.map(
|
||||
(e) => ColumnConditionCompat(
|
||||
column: e.key,
|
||||
operator_: ColumnConditionCompatOperator.eq,
|
||||
value: e.value,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
final respData = resp.data;
|
||||
if (respData == null) {
|
||||
throw Exception("No data in response");
|
||||
}
|
||||
|
||||
refreshTableData();
|
||||
} on DioError catch (e) {
|
||||
final respData = e.response?.data;
|
||||
if (respData != null) {
|
||||
Get.snackbar(
|
||||
"Error trying to update table data",
|
||||
"${respData['error']}",
|
||||
);
|
||||
} else {
|
||||
Get.snackbar(
|
||||
"Error trying to update table data",
|
||||
"$e",
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
Get.snackbar(
|
||||
"Error trying to update table data",
|
||||
"$e",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class OpenTableDialog extends GetView<OpenTableController> {
|
||||
const OpenTableDialog({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Row(
|
||||
children: [
|
||||
Text(controller.table.tableName.pascalCase),
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
onPressed: () => controller.refreshTableData(),
|
||||
icon: const Icon(Icons.refresh),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
},
|
||||
icon: const Icon(Icons.close),
|
||||
),
|
||||
],
|
||||
),
|
||||
content: Obx(
|
||||
() => DataTable2(
|
||||
columns: [
|
||||
for (final col in controller.table.parsedColumns)
|
||||
DataColumn(
|
||||
label: Text(
|
||||
col.name.pascalCase,
|
||||
),
|
||||
),
|
||||
const DataColumn(label: Text("Actions")),
|
||||
],
|
||||
empty: const Text("No data"),
|
||||
rows: [
|
||||
DataRow(
|
||||
cells: [
|
||||
for (final col in controller.table.parsedColumns)
|
||||
if (col is PrimarySerialColumnDefinition)
|
||||
const DataCell(
|
||||
Text(
|
||||
"AUTO",
|
||||
),
|
||||
)
|
||||
else if (col is TextColumnDefinition)
|
||||
DataCell(
|
||||
Obx(
|
||||
() => TextField(
|
||||
controller: TextEditingController(
|
||||
text: controller.newRowData[col.name] ?? ""),
|
||||
decoration: InputDecoration(
|
||||
label: Text(col.name.pascalCase),
|
||||
),
|
||||
onChanged: (value) => controller.setNewRowData(
|
||||
col.name,
|
||||
value,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
else if (col is BooleanColumnDefinition)
|
||||
DataCell(
|
||||
Obx(
|
||||
() => Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: controller.newRowData[col.name] ?? false,
|
||||
onChanged: (value) => controller.setNewRowData(
|
||||
col.name,
|
||||
value,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
else if (col is TimestampColumnDefinition)
|
||||
DataCell(
|
||||
Obx(
|
||||
() => TextField(
|
||||
controller: TextEditingController(
|
||||
text: () {
|
||||
final dt = controller.newRowData[col.name];
|
||||
if (dt == null || dt is! DateTime) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return postgresDateFormat(dt);
|
||||
}(),
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
label: Text(col.name.pascalCase),
|
||||
),
|
||||
readOnly: true,
|
||||
onTap: () async {
|
||||
final dt = await showOmniDateTimePicker(
|
||||
context: context,
|
||||
is24HourMode: true,
|
||||
isForce2Digits: true,
|
||||
);
|
||||
if (dt != null) {
|
||||
controller.setNewRowData(
|
||||
col.name,
|
||||
dt,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
else if (col is DoubleColumnDefinition)
|
||||
DataCell(
|
||||
Obx(
|
||||
() => TextField(
|
||||
controller: TextEditingController(
|
||||
text: (controller.newRowData[col.name] as double?)
|
||||
?.toString() ??
|
||||
""),
|
||||
decoration: InputDecoration(
|
||||
label: Text(col.name.pascalCase),
|
||||
),
|
||||
onChanged: (value) => controller.setNewRowData(
|
||||
col.name,
|
||||
double.tryParse(value),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
else if (col is IntegerColumnDefinition)
|
||||
DataCell(
|
||||
Obx(
|
||||
() => TextField(
|
||||
controller: TextEditingController(
|
||||
text: (controller.newRowData[col.name] as int?)
|
||||
?.toString() ??
|
||||
""),
|
||||
decoration: InputDecoration(
|
||||
label: Text(col.name.pascalCase),
|
||||
),
|
||||
onChanged: (value) => controller.setNewRowData(
|
||||
col.name,
|
||||
int.tryParse(value),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
else if (col is UserRefColumnDefinition)
|
||||
DataCell(Obx(
|
||||
() => TextField(
|
||||
controller: TextEditingController(
|
||||
text: (controller.newRowData[col.name]
|
||||
as UserDefinition?)
|
||||
?.username ??
|
||||
""),
|
||||
decoration: InputDecoration(
|
||||
label: Text(col.name.pascalCase),
|
||||
),
|
||||
readOnly: true,
|
||||
onTap: () async {
|
||||
final user = await controller.showUserPicker();
|
||||
if (user == null) return;
|
||||
|
||||
controller.setNewRowData(
|
||||
col.name,
|
||||
user,
|
||||
);
|
||||
},
|
||||
),
|
||||
))
|
||||
else if (col is AssetRefColumnDefinition)
|
||||
DataCell(Obx(
|
||||
() => TextField(
|
||||
controller: TextEditingController(
|
||||
text: (controller.newRowData[col.name] as Asset?)
|
||||
?.name ??
|
||||
""),
|
||||
decoration: InputDecoration(
|
||||
label: Text(col.name.pascalCase),
|
||||
),
|
||||
readOnly: true,
|
||||
onTap: () async {
|
||||
final asset = await controller.showAssetPicker();
|
||||
if (asset == null) return;
|
||||
|
||||
controller.setNewRowData(
|
||||
col.name,
|
||||
asset,
|
||||
);
|
||||
},
|
||||
),
|
||||
))
|
||||
else
|
||||
DataCell.empty,
|
||||
DataCell(Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
controller.addNewRow();
|
||||
},
|
||||
icon: const Icon(Icons.add),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => controller.clearNewRowData(),
|
||||
icon: const Icon(Icons.clear),
|
||||
),
|
||||
],
|
||||
)),
|
||||
],
|
||||
),
|
||||
...controller.tableData.map((e) {
|
||||
return DataRow(
|
||||
cells: [
|
||||
for (final col in controller.table.parsedColumns)
|
||||
if (col is PrimarySerialColumnDefinition)
|
||||
DataCell(
|
||||
Tooltip(
|
||||
message: e[col.name].toString(),
|
||||
child: Text(
|
||||
e[col.name].toString(),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
)
|
||||
else if (col is TextColumnDefinition)
|
||||
DataCell(
|
||||
Tooltip(
|
||||
message: e[col.name].toString(),
|
||||
child: Text(
|
||||
e[col.name].toString(),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
onDoubleTap: () async {
|
||||
final text = await showStringInputDialog(
|
||||
originalValue: e[col.name].toString());
|
||||
if (text != null) {
|
||||
await controller.updateItem(
|
||||
e,
|
||||
col.name,
|
||||
text,
|
||||
);
|
||||
}
|
||||
},
|
||||
)
|
||||
else if (col is BooleanColumnDefinition)
|
||||
DataCell(
|
||||
Checkbox(
|
||||
value: e[col.name],
|
||||
onChanged: (v) async {
|
||||
await controller.updateItem(
|
||||
e,
|
||||
col.name,
|
||||
v ?? false,
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
else if (col is TimestampColumnDefinition)
|
||||
DataCell(
|
||||
() {
|
||||
final msg = () {
|
||||
final dt = e[col.name];
|
||||
if (dt == null) {
|
||||
return "#error#";
|
||||
}
|
||||
if (dt is String) {
|
||||
final rdt = DateTime.parse(dt);
|
||||
return postgresDateFormat(rdt);
|
||||
}
|
||||
|
||||
return postgresDateFormat(dt);
|
||||
}();
|
||||
return Tooltip(
|
||||
message: msg,
|
||||
child: Text(
|
||||
msg,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
);
|
||||
}(),
|
||||
onDoubleTap: () async {
|
||||
final dt = await showOmniDateTimePicker(
|
||||
context: context,
|
||||
is24HourMode: true,
|
||||
isForce2Digits: true,
|
||||
);
|
||||
if (dt != null) {
|
||||
await controller.updateItem(
|
||||
e,
|
||||
col.name,
|
||||
dt,
|
||||
);
|
||||
}
|
||||
},
|
||||
)
|
||||
else if (col is DoubleColumnDefinition)
|
||||
DataCell(
|
||||
Tooltip(
|
||||
message: e[col.name].toString(),
|
||||
child: Text(
|
||||
e[col.name].toString(),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
onDoubleTap: () async {
|
||||
final dblVal = await showDoubleInputDialog(
|
||||
originalValue: e[col.name]);
|
||||
if (dblVal != null) {
|
||||
await controller.updateItem(
|
||||
e,
|
||||
col.name,
|
||||
dblVal,
|
||||
);
|
||||
}
|
||||
},
|
||||
)
|
||||
else if (col is IntegerColumnDefinition)
|
||||
DataCell(
|
||||
Tooltip(
|
||||
message: e[col.name].toString(),
|
||||
child: Text(
|
||||
e[col.name].toString(),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
onDoubleTap: () async {
|
||||
final intVal = await showIntInputDialog(
|
||||
originalValue: e[col.name]);
|
||||
if (intVal != null) {
|
||||
await controller.updateItem(
|
||||
e,
|
||||
col.name,
|
||||
intVal,
|
||||
);
|
||||
}
|
||||
},
|
||||
)
|
||||
else if (col is UserRefColumnDefinition)
|
||||
DataCell(
|
||||
FutureBuilder<String>(
|
||||
future: () async {
|
||||
final cachedUser =
|
||||
controller.getUserFromCache(e[col.name]);
|
||||
if (cachedUser != null) {
|
||||
return cachedUser.username;
|
||||
}
|
||||
|
||||
final user = await ApiController.to.apiClient
|
||||
.getItemsFromTable(
|
||||
tableName: "users",
|
||||
itemsSelector: ItemsSelector(
|
||||
fields: ["username"],
|
||||
where: [
|
||||
ColumnConditionCompat(
|
||||
column: "id",
|
||||
operator_: ColumnConditionCompatOperator.eq,
|
||||
value: e[col.name],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
final ud = user.data;
|
||||
if (ud == null ||
|
||||
ud.isEmpty ||
|
||||
ud.first["username"] == null) {
|
||||
return "#error#";
|
||||
}
|
||||
|
||||
controller.putUserInCache(UserDefinition(
|
||||
id: e[col.name],
|
||||
username: ud.first["username"],
|
||||
password: "",
|
||||
accessToken: "",
|
||||
));
|
||||
|
||||
return ud.first["username"].toString();
|
||||
}(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState ==
|
||||
ConnectionState.waiting) {
|
||||
return const CircularProgressIndicator();
|
||||
}
|
||||
|
||||
return Tooltip(
|
||||
message: snapshot.data ?? "#error#",
|
||||
child: Text(
|
||||
snapshot.data ?? "#error#",
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
onDoubleTap: () async {
|
||||
final user = await controller.showUserPicker();
|
||||
if (user != null) {
|
||||
await controller.updateItem(
|
||||
e,
|
||||
col.name,
|
||||
user.id,
|
||||
);
|
||||
}
|
||||
},
|
||||
)
|
||||
else if (col is AssetRefColumnDefinition)
|
||||
DataCell(
|
||||
FutureBuilder<String>(
|
||||
future: () async {
|
||||
final cachedAsset =
|
||||
controller.getAssetFromCache(e[col.name]);
|
||||
if (cachedAsset != null) {
|
||||
return cachedAsset.name;
|
||||
}
|
||||
|
||||
final asset = await ApiController.to.apiClient
|
||||
.getItemsFromTable(
|
||||
tableName: "assets",
|
||||
itemsSelector: ItemsSelector(
|
||||
fields: ["name"],
|
||||
where: [
|
||||
ColumnConditionCompat(
|
||||
column: "id",
|
||||
operator_: ColumnConditionCompatOperator.eq,
|
||||
value: e[col.name],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
final ad = asset.data;
|
||||
if (ad == null ||
|
||||
ad.isEmpty ||
|
||||
ad.first["name"] == null) {
|
||||
return "#error#";
|
||||
}
|
||||
|
||||
controller.putAssetInCache(Asset(
|
||||
id: e[col.name],
|
||||
name: ad.first["name"],
|
||||
description: "",
|
||||
fid: "",
|
||||
tags: "",
|
||||
));
|
||||
|
||||
return ad.first["name"].toString();
|
||||
}(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState ==
|
||||
ConnectionState.waiting) {
|
||||
return const CircularProgressIndicator();
|
||||
}
|
||||
|
||||
return Tooltip(
|
||||
message: snapshot.data ?? "#error#",
|
||||
child: Text(
|
||||
snapshot.data ?? "#error#",
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
onDoubleTap: () async {
|
||||
final asset = await controller.showAssetPicker();
|
||||
if (asset != null) {
|
||||
await controller.updateItem(
|
||||
e,
|
||||
col.name,
|
||||
asset.id,
|
||||
);
|
||||
}
|
||||
},
|
||||
)
|
||||
else
|
||||
DataCell.empty,
|
||||
DataCell(Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () => controller.deleteItem(e),
|
||||
icon: const Icon(Icons.delete),
|
||||
),
|
||||
],
|
||||
)),
|
||||
],
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
).constrained(width: Get.width * 0.9, height: Get.height * 0.9),
|
||||
);
|
||||
}
|
||||
|
||||
static Future<void> show(TableDefinition table) async {
|
||||
Get.lazyPut<OpenTableController>(() => OpenTableController(table: table));
|
||||
|
||||
await Get.dialog(
|
||||
const OpenTableDialog(),
|
||||
barrierDismissible: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import 'package:tuuli_app/api_controller.dart';
|
|||
import 'package:recase/recase.dart';
|
||||
import 'package:tuuli_app/models/db_column_definition.dart';
|
||||
import 'package:tuuli_app/pages/dialogs/create_table_dialog.dart';
|
||||
import 'package:tuuli_app/pages/dialogs/open_table_dialog.dart';
|
||||
|
||||
class TablesListPanelController extends GetxController {
|
||||
@override
|
||||
|
|
@ -62,7 +63,9 @@ class TablesListPanelController extends GetxController {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> openTable(TableDefinition table) async {}
|
||||
Future<void> openTable(TableDefinition table) async {
|
||||
await OpenTableDialog.show(table);
|
||||
}
|
||||
}
|
||||
|
||||
class TablesListPanel extends GetView<TablesListPanelController> {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:tuuli_api/tuuli_api.dart';
|
||||
import 'package:tuuli_app/models/user_definition.dart';
|
||||
|
||||
Random _random = Random();
|
||||
|
||||
String randomHexString(int length) {
|
||||
|
|
@ -9,3 +12,35 @@ String randomHexString(int length) {
|
|||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
String postgresDateFormat(DateTime dt) {
|
||||
int yearSign = dt.year.sign;
|
||||
int absYear = dt.year.abs();
|
||||
String y = absYear
|
||||
.toString()
|
||||
.padLeft((dt.year >= -9999 && dt.year <= 9999) ? 4 : 6, "0");
|
||||
if (yearSign == -1) {
|
||||
y = "-$y";
|
||||
}
|
||||
String m = dt.month.toString().padLeft(2, "0");
|
||||
String d = dt.day.toString().padLeft(2, "0");
|
||||
String h = dt.hour.toString().padLeft(2, "0");
|
||||
String min = dt.minute.toString().padLeft(2, "0");
|
||||
String sec = dt.second.toString().padLeft(2, "0");
|
||||
|
||||
return "$y-$m-$d $h:$min:$sec";
|
||||
}
|
||||
|
||||
Map<String, dynamic> convertToPayload(Map<String, dynamic> data) {
|
||||
return data.map((key, value) {
|
||||
if (value is UserDefinition) {
|
||||
return MapEntry(key, value.id);
|
||||
} else if (value is Asset) {
|
||||
return MapEntry(key, value.id);
|
||||
} else if (value is DateTime) {
|
||||
return MapEntry(key, postgresDateFormat(value));
|
||||
}
|
||||
|
||||
return MapEntry(key, value);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
126
lib/widgets/data_input_dialog.dart
Normal file
126
lib/widgets/data_input_dialog.dart
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_fast_forms/flutter_fast_forms.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
Future<String?> showStringInputDialog({String? originalValue}) async {
|
||||
final strVal = (originalValue ?? "").obs;
|
||||
return await Get.dialog<String>(
|
||||
AlertDialog(
|
||||
title: const Text("Enter a string"),
|
||||
content: TextField(
|
||||
controller: TextEditingController(text: originalValue),
|
||||
onChanged: (value) {
|
||||
strVal.value = value;
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Get.back(result: null);
|
||||
},
|
||||
child: const Text("Cancel"),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Get.back(result: strVal.value);
|
||||
},
|
||||
child: const Text("OK"),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<double?> showDoubleInputDialog({double? originalValue}) async {
|
||||
final strVal = (originalValue?.toString() ?? "").obs;
|
||||
return await Get.dialog<double>(
|
||||
AlertDialog(
|
||||
title: const Text("Enter a number"),
|
||||
content: FastTextField(
|
||||
name: "Number",
|
||||
initialValue: originalValue?.toString(),
|
||||
keyboardType: TextInputType.number,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return "Please enter a number";
|
||||
}
|
||||
final parsed = double.tryParse(value);
|
||||
if (parsed == null) {
|
||||
return "Please enter a valid number";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onChanged: (value) {
|
||||
strVal.value = value ?? "";
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Get.back(result: null);
|
||||
},
|
||||
child: const Text("Cancel"),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
final d = double.tryParse(strVal.value);
|
||||
if (d != null) {
|
||||
Get.back(result: d);
|
||||
} else {
|
||||
Get.back(result: null);
|
||||
}
|
||||
},
|
||||
child: const Text("OK"),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<int?> showIntInputDialog({int? originalValue}) async {
|
||||
final strVal = (originalValue?.toString() ?? "").obs;
|
||||
return await Get.dialog<int>(
|
||||
AlertDialog(
|
||||
title: const Text("Enter a number"),
|
||||
content: FastTextField(
|
||||
name: "Number",
|
||||
initialValue: originalValue?.toString(),
|
||||
keyboardType: TextInputType.number,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return "Please enter a number";
|
||||
}
|
||||
final parsed = int.tryParse(value);
|
||||
if (parsed == null) {
|
||||
return "Please enter a valid number";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onChanged: (value) {
|
||||
strVal.value = value ?? "";
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Get.back(result: null);
|
||||
},
|
||||
child: const Text("Cancel"),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
final i = int.tryParse(strVal.value);
|
||||
if (i != null) {
|
||||
Get.back(result: i);
|
||||
} else {
|
||||
Get.back(result: null);
|
||||
}
|
||||
},
|
||||
child: const Text("OK"),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue