943 lines
33 KiB
Dart
943 lines
33 KiB
Dart
import 'package:data_table_2/data_table_2.dart';
|
||
import 'package:dio/dio.dart';
|
||
import 'package:flutter/material.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("В ответе нет данных");
|
||
}
|
||
|
||
_tableData.clear();
|
||
_tableData.addAll(respData);
|
||
} on DioError catch (e) {
|
||
final respData = e.response?.data;
|
||
if (respData != null) {
|
||
Get.snackbar(
|
||
"Ошибка получения данных таблицы",
|
||
"${respData['error']}",
|
||
);
|
||
} else {
|
||
Get.snackbar(
|
||
"Ошибка получения данных таблицы",
|
||
"$e",
|
||
);
|
||
}
|
||
} catch (e) {
|
||
Get.snackbar(
|
||
"Ошибка получения данных таблицы",
|
||
"$e",
|
||
);
|
||
}
|
||
}
|
||
|
||
Future<UserDefinition?> showUserPicker() async {
|
||
final username = "".obs;
|
||
|
||
final user = await Get.dialog<UserDefinition>(
|
||
AlertDialog(
|
||
title: const Text("Выберите пользователя"),
|
||
content: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
TextField(
|
||
onChanged: (value) {
|
||
username.value = value;
|
||
},
|
||
decoration: const InputDecoration(
|
||
labelText: "Логин",
|
||
),
|
||
),
|
||
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("В ответе нет данных");
|
||
}
|
||
|
||
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("Отменить"),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
|
||
return user;
|
||
}
|
||
|
||
Future<Asset?> showAssetPicker() async {
|
||
final name = "".obs;
|
||
|
||
final asset = await Get.dialog<Asset>(
|
||
AlertDialog(
|
||
title: const Text("Выберите ресурс"),
|
||
content: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
TextField(
|
||
onChanged: (value) {
|
||
name.value = value;
|
||
},
|
||
decoration: const InputDecoration(
|
||
labelText: "Имя файла",
|
||
),
|
||
),
|
||
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("В ответе нет данных");
|
||
}
|
||
|
||
return respData
|
||
.map((e) => Asset(
|
||
id: e["id"],
|
||
name: e["name"],
|
||
description: e["description"],
|
||
fid: "",
|
||
tags: "",
|
||
mime: "",
|
||
))
|
||
.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("Отменить"),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
|
||
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("В ответе нет данных");
|
||
}
|
||
|
||
clearNewRowData();
|
||
refreshTableData();
|
||
} on DioError catch (e) {
|
||
final respData = e.response?.data;
|
||
if (respData != null) {
|
||
Get.snackbar(
|
||
"Ошибка получения данных таблицы",
|
||
"${respData['error']}",
|
||
);
|
||
} else {
|
||
Get.snackbar(
|
||
"Ошибка получения данных таблицы",
|
||
"$e",
|
||
);
|
||
}
|
||
} catch (e) {
|
||
Get.snackbar(
|
||
"Ошибка получения данных таблицы",
|
||
"$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("В ответе нет данных");
|
||
}
|
||
|
||
refreshTableData();
|
||
} on DioError catch (e) {
|
||
final respData = e.response?.data;
|
||
if (respData != null) {
|
||
Get.snackbar(
|
||
"Ошибка обновления данных таблицы",
|
||
"${respData['error']}",
|
||
);
|
||
} else {
|
||
Get.snackbar(
|
||
"Ошибка обновления данных таблицы",
|
||
"$e",
|
||
);
|
||
}
|
||
} catch (e) {
|
||
Get.snackbar(
|
||
"Ошибка обновления данных таблицы",
|
||
"$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("В ответе нет данных");
|
||
}
|
||
|
||
refreshTableData();
|
||
} on DioError catch (e) {
|
||
final respData = e.response?.data;
|
||
if (respData != null) {
|
||
Get.snackbar(
|
||
"Ошибка удаления данных таблицы",
|
||
"${respData['error']}",
|
||
);
|
||
} else {
|
||
Get.snackbar(
|
||
"Ошибка удаления данных таблицы",
|
||
"$e",
|
||
);
|
||
}
|
||
} catch (e) {
|
||
Get.snackbar(
|
||
"Ошибка удаления данных таблицы",
|
||
"$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("Действия")),
|
||
],
|
||
empty: const Text("Нет данных"),
|
||
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 "###";
|
||
}
|
||
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 "###";
|
||
}
|
||
|
||
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 ?? "###",
|
||
child: Text(
|
||
snapshot.data ?? "###",
|
||
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 "###";
|
||
}
|
||
|
||
controller.putAssetInCache(Asset(
|
||
id: e[col.name],
|
||
name: ad.first["name"],
|
||
description: "",
|
||
fid: "",
|
||
tags: "",
|
||
mime: "",
|
||
));
|
||
|
||
return ad.first["name"].toString();
|
||
}(),
|
||
builder: (context, snapshot) {
|
||
if (snapshot.connectionState ==
|
||
ConnectionState.waiting) {
|
||
return const CircularProgressIndicator();
|
||
}
|
||
|
||
return Tooltip(
|
||
message: snapshot.data ?? "###",
|
||
child: Text(
|
||
snapshot.data ?? "###",
|
||
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,
|
||
);
|
||
}
|
||
}
|