use std::collections::HashMap;
use crate::admin::auto_form::FormBuilder;
use crate::admin::form::{FieldConfig, FieldType, FormConfig};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AdminDataType {
String,
Text,
Integer,
Float,
Boolean,
DateTime,
Email,
}
#[derive(Debug, Clone)]
pub struct AdminUiField {
pub name: &'static str,
pub label: &'static str,
pub data_type: AdminDataType,
pub required: bool,
pub readonly: bool,
pub is_relation: bool,
pub options: Vec<(String, String)>,
pub filterable: bool,
pub advanced_filter: bool,
pub sortable: bool,
pub visible_in_table: bool,
}
impl AdminUiField {
fn base(name: &'static str, label: &'static str, data_type: AdminDataType) -> Self {
Self {
name,
label,
data_type,
required: false,
readonly: false,
is_relation: false,
options: Vec::new(),
filterable: false,
advanced_filter: false,
sortable: false,
visible_in_table: true,
}
}
pub fn text(name: &'static str, label: &'static str) -> Self {
Self::base(name, label, AdminDataType::String)
}
pub fn textarea(name: &'static str, label: &'static str) -> Self {
Self::base(name, label, AdminDataType::Text)
}
pub fn integer(name: &'static str, label: &'static str) -> Self {
Self::base(name, label, AdminDataType::Integer)
}
pub fn float(name: &'static str, label: &'static str) -> Self {
Self::base(name, label, AdminDataType::Float)
}
pub fn boolean(name: &'static str, label: &'static str) -> Self {
Self::base(name, label, AdminDataType::Boolean)
}
pub fn datetime(name: &'static str, label: &'static str) -> Self {
Self::base(name, label, AdminDataType::DateTime)
}
pub fn email(name: &'static str, label: &'static str) -> Self {
Self::base(name, label, AdminDataType::Email)
}
pub fn required(mut self, value: bool) -> Self {
self.required = value;
self
}
pub fn readonly(mut self, value: bool) -> Self {
self.readonly = value;
self
}
pub fn relation(mut self, value: bool) -> Self {
self.is_relation = value;
self
}
pub fn options(mut self, options: Vec<(String, String)>) -> Self {
self.options = options;
self
}
pub fn filterable(mut self, value: bool) -> Self {
self.filterable = value;
self
}
pub fn advanced_filter(mut self, value: bool) -> Self {
self.advanced_filter = value;
self
}
pub fn sortable(mut self, value: bool) -> Self {
self.sortable = value;
self
}
pub fn visible_in_table(mut self, value: bool) -> Self {
self.visible_in_table = value;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FilterType {
Boolean,
Select,
Exact,
}
pub fn resolve_filter_type(field: &AdminUiField) -> FilterType {
if field.data_type == AdminDataType::Boolean {
FilterType::Boolean
} else if field.is_relation || !field.options.is_empty() {
FilterType::Select
} else {
FilterType::Exact
}
}
pub trait AdminUiModel: Send + Sync + 'static {
fn slug(&self) -> &'static str;
fn model_name(&self) -> &'static str;
fn table_name(&self) -> &'static str;
fn primary_key(&self) -> &'static str;
fn fields(&self) -> Vec<AdminUiField>;
fn searchable_fields(&self) -> Vec<&'static str>;
fn primary_status_field(&self) -> Option<&'static str>;
fn ensure_table_sql(&self) -> Option<&'static str>;
}
pub fn form_from_admin_ui_model(model: &dyn AdminUiModel) -> FormConfig {
let fields = model.fields().into_iter().map(field_config_from).collect();
FormConfig {
title: format!("Edit {}", model.model_name()),
subtitle: String::new(),
fields,
submitted: false,
save_failed: false,
hidden_fields: Vec::new(),
}
}
fn field_config_from(f: AdminUiField) -> FieldConfig {
let mut ty = match f.data_type {
AdminDataType::String => FieldType::Text,
AdminDataType::Text => FieldType::TextArea,
AdminDataType::Integer => FieldType::Number,
AdminDataType::Float => FieldType::Number,
AdminDataType::Boolean => FieldType::Boolean,
AdminDataType::DateTime => FieldType::DateTime,
AdminDataType::Email => FieldType::Email,
};
if f.is_relation {
ty = FieldType::ForeignKey;
}
if !f.options.is_empty() && ty != FieldType::Boolean && !f.is_relation {
ty = FieldType::Select;
}
FieldConfig {
name: f.name.to_string(),
label: f.label.to_string(),
field_type: ty,
required: f.required,
readonly: f.readonly,
placeholder: None,
help: None,
value: None,
options: f.options,
error: None,
}
}
impl FormBuilder {
pub fn from_admin_ui_model(model: &dyn AdminUiModel) -> Self {
Self {
form: form_from_admin_ui_model(model),
}
}
}
pub type ModelFactory = Box<dyn Fn() -> Box<dyn AdminUiModel> + Send + Sync>;
pub struct AdminRegistry {
factories: HashMap<&'static str, ModelFactory>,
}
impl AdminRegistry {
pub fn new() -> Self {
Self {
factories: HashMap::new(),
}
}
pub fn register<F>(&mut self, slug: &'static str, factory: F)
where
F: Fn() -> Box<dyn AdminUiModel> + Send + Sync + 'static,
{
self.factories.insert(slug, Box::new(factory));
}
pub fn get(&self, slug: &str) -> Option<Box<dyn AdminUiModel>> {
self.factories.get(slug).map(|f| f())
}
pub fn slugs(&self) -> impl Iterator<Item = &&'static str> {
self.factories.keys()
}
}
impl Default for AdminRegistry {
fn default() -> Self {
Self::new()
}
}