canonrs-server 0.1.0

CanonRS server-side rendering support
#![allow(unreachable_pub, dead_code)]
//! DataTable Full - HTML estático, comportamento delegado ao behavior JS

use leptos::prelude::*;
use crate::ui::scroll_area::scroll_area_boundary::ScrollArea;
use std::sync::Arc;
use canonrs_core::primitives::{
    DataTableBulkBarPrimitive,
    DataTablePrimitive, DataTableToolbarPrimitive,
    DataTableTablePrimitive, DataTableHeadPrimitive, DataTableHeadRowPrimitive,
    DataTableHeadCellPrimitive, DataTableBodyPrimitive, DataTableRowPrimitive,
    DataTableCellPrimitive, DataTableEmptyPrimitive,
    DataTableColgroupPrimitive, DataTableColPrimitive,
    DataTableExpandHeadCellPrimitive, DataTableExpandCellPrimitive,
    DataTableExpandBtnPrimitive, DataTableExpandRowPrimitive,
    DataTableDensity, SortDirection,
};
use crate::ui::dropdown_menu::{
    DropdownMenu, DropdownMenuItem, DropdownMenuCheckboxItem,
};
use super::types::{RowIdFn, RowLabelFn, ExpandRenderFn};
use crate::ui::context_menu::context_menu_boundary::{
    ContextMenuContent, ContextMenuItem,
};

#[derive(Clone)]
pub struct DataTableColumn<T> {
    pub key: String,
    pub label: String,
    pub render: Arc<dyn Fn(&T) -> String + Send + Sync>,
}

impl<T> DataTableColumn<T> {
    pub fn new(key: impl Into<String>, label: impl Into<String>, render: impl Fn(&T) -> String + Send + Sync + 'static) -> Self {
        Self { key: key.into(), label: label.into(), render: Arc::new(render) }
    }
}

#[derive(Clone)]
pub struct BulkAction {
    pub id: &'static str,
    pub label: &'static str,
    pub danger: bool,
}

impl BulkAction {
    pub fn new(id: &'static str, label: &'static str) -> Self {
        Self { id, label, danger: false }
    }
    pub fn danger(mut self) -> Self { self.danger = true; self }
}

#[derive(Clone)]
pub struct RowAction {
    pub id: &'static str,
    pub label: &'static str,
    pub danger: bool,
    pub inline: bool,
}

impl RowAction {
    pub fn new(id: &'static str, label: &'static str) -> Self {
        Self { id, label, danger: false, inline: false }
    }
    pub fn danger(mut self) -> Self { self.danger = true; self }
    pub fn inline(mut self) -> Self { self.inline = true; self }
}

#[component]
pub fn DataTableStatic<T>(
    data: Vec<T>,
    columns: Vec<DataTableColumn<T>>,
    #[prop(default = DataTableDensity::default())] density: DataTableDensity,
    #[prop(into, default = String::new())] class: String,
    #[prop(default = 10)] page_size: usize,
    #[prop(default = false)] selectable: bool,
    #[prop(into, default = String::new())] sync_chart: String,
    #[prop(into, default = String::new())] sync_scope: String,
    #[prop(default = false)] show_density: bool,
    #[prop(default = false)] resizable: bool,
    #[prop(default = ExpandRenderFn::default())] expand_render: ExpandRenderFn<T>,
    #[prop(default = vec![])] row_actions: Vec<RowAction>,
    #[prop(default = vec![])] bulk_actions: Vec<BulkAction>,
    #[prop(default = RowIdFn::default())] row_id_fn: RowIdFn<T>,
    #[prop(default = RowLabelFn::default())] row_label_fn: RowLabelFn<T>,
) -> impl IntoView
where
    T: Clone + Send + Sync + 'static,
{
    let total = data.len();
    let total_pages = ((total as f64) / (page_size as f64)).ceil().max(1.0) as usize;
    let has_expand = expand_render.0.is_some();
    let col_count = columns.len()
        + if selectable { 1 } else { 0 }
        + if has_expand { 1 } else { 0 }
        + if !row_actions.is_empty() { 1 } else { 0 };
    let visible_data = StoredValue::new(data.into_iter().enumerate().collect::<Vec<_>>());
    let cols = StoredValue::new(columns.clone());
    let expand_render = StoredValue::new(expand_render.0);
    let row_actions = StoredValue::new(row_actions);
    let bulk_actions = StoredValue::new(bulk_actions);
    let row_id_fn = StoredValue::new(row_id_fn.0);
    let row_label_fn = StoredValue::new(row_label_fn.0);
    let initial_density = density.as_str();

    view! {
        <DataTablePrimitive
            density=density
            class=class
            attr:data-rs-page-size=page_size.to_string()
            attr:data-rs-current-page="1"
            attr:data-rs-total-pages=total_pages.to_string()
            attr:data-rs-selectable={selectable.then(|| "true")}
            attr:data-rs-chart-sync=sync_chart.clone()
            attr:data-rs-chart-sync-scope=sync_scope.clone()
        >
            <DataTableBulkBarPrimitive>
                <span data-rs-datatable-bulk-count="">"0 selected"</span>
                <div data-rs-datatable-bulk-actions="">
                    {bulk_actions.get_value().into_iter().map(|action| {
                        view! {
                            <button
                                type="button"
                                data-rs-datatable-bulk-action=action.id
                                class={if action.danger { "danger".to_string() } else { String::new() }}
                            >
                                {action.label}
                            </button>
                        }
                    }).collect::<Vec<_>>()}
                </div>
                <button type="button" data-rs-datatable-bulk-clear="">"✕ Clear"</button>
            </DataTableBulkBarPrimitive>
            <DataTableToolbarPrimitive>
                <input type="text" data-rs-datatable-filter="" placeholder="Search..." />
                <div data-rs-datatable-density-toggle="" hidden=(!show_density)>
                    <button type="button" data-rs-density-btn="compact"
                        data-active={if initial_density == "compact" { "true" } else { "false" }}>
                        "Compact"
                    </button>
                    <button type="button" data-rs-density-btn="comfortable"
                        data-active={if initial_density == "comfortable" { "true" } else { "false" }}>
                        "Comfortable"
                    </button>
                    <button type="button" data-rs-density-btn="spacious"
                        data-active={if initial_density == "spacious" { "true" } else { "false" }}>
                        "Spacious"
                    </button>
                </div>
                <DropdownMenu trigger_label="Columns">
                    {columns.iter().enumerate().map(|(idx, col)| {
                        let label = col.label.clone();
                        view! {
                            <DropdownMenuCheckboxItem attr:data-rs-col-index=idx.to_string()>
                                {label}
                            </DropdownMenuCheckboxItem>
                        }
                    }).collect::<Vec<_>>()}
                </DropdownMenu>
            </DataTableToolbarPrimitive>

            <ScrollArea orientation=canonrs_core::primitives::ScrollOrientation::Horizontal auto_hide=false>
            <DataTableTablePrimitive resizable=resizable>
                <DataTableColgroupPrimitive>
                    <col data-rs-datatable-col="" data-rs-col-expand="" class=if has_expand { "" } else { "rs-col-hidden" } />
                    <col data-rs-datatable-col="" data-rs-col-select="" class=if selectable { "" } else { "rs-col-hidden" } />
                    {cols.get_value().into_iter().enumerate().map(|(idx, _)| {
                        view! { <DataTableColPrimitive col_index=idx.to_string() /> }
                    }).collect::<Vec<_>>()}
                    <col data-rs-datatable-col="" data-rs-col-actions="" class=if !row_actions.get_value().is_empty() { "" } else { "rs-col-hidden" } />
                </DataTableColgroupPrimitive>
                <DataTableHeadPrimitive>
                    <DataTableHeadRowPrimitive>
                        <DataTableExpandHeadCellPrimitive class=if has_expand { "" } else { "rs-col-hidden" } />
                        <th data-rs-datatable-head-cell="" scope="col" data-rs-col-select="" class=if selectable { "" } else { "rs-col-hidden" }>
                            <input type="checkbox" data-rs-datatable-select-all="" />
                        </th>
                        {cols.get_value().into_iter().enumerate().map(|(idx, col)| {
                            let key = col.key.clone();
                            let label = col.label.clone();
                            view! {
                                <DataTableHeadCellPrimitive
                                    sort_key=key
                                    sort_direction=SortDirection::None
                                    col_index=idx.to_string()
                                    resizable=resizable
                                >
                                    <span data-rs-datatable-head-label="">{label}</span>
                                    <span data-rs-datatable-sort-icon="" aria-hidden="true">""</span>
                                </DataTableHeadCellPrimitive>
                            }
                        }).collect::<Vec<_>>()}
                    </DataTableHeadRowPrimitive>
                </DataTableHeadPrimitive>

                <DataTableBodyPrimitive>
                    {visible_data.get_value().into_iter().map(|(idx, row)| {
                        let row_cols = cols.get_value();
                        let expand_content: Option<AnyView> = expand_render.get_value().as_ref().map(|f| f(&row));
                        let has_expand_row = expand_content.is_some();
                        let has_actions = !row_actions.get_value().is_empty();
                        let ctx_actions = row_actions.get_value();
                        let row_label = row_label_fn.get_value().as_ref().map(|f| f(&row)).unwrap_or_default();
                        let real_id = row_id_fn.get_value().as_ref().map(|f| f(&row)).unwrap_or_else(|| idx.to_string());
                        let real_id = StoredValue::new(real_id);
                        let ctx_row_id = real_id;

                        let main_row = view! {
                            <DataTableRowPrimitive row_id=real_id.get_value() row_label=row_label row_index=idx>
                                {
                                    let rid = real_id.get_value();
                                    view! {
                                        <DataTableExpandCellPrimitive row_id=rid.clone() class=if has_expand { "" } else { "rs-col-hidden" }>
                                            {if has_expand { Some(view! { <DataTableExpandBtnPrimitive row_id=rid /> }) } else { None }}
                                        </DataTableExpandCellPrimitive>
                                    }
                                }
                                <td data-rs-datatable-cell="" data-rs-col-select="" class=if selectable { "" } else { "rs-col-hidden" }>
                                    <input type="checkbox" data-rs-datatable-select-row="" value=idx.to_string() />
                                </td>
                                {row_cols.into_iter().enumerate().map(|(col_idx, col)| {
                                    let value = (col.render)(&row);
                                    view! {
                                        <DataTableCellPrimitive col_index=col_idx.to_string()>
                                            {value}
                                        </DataTableCellPrimitive>
                                    }
                                }).collect::<Vec<_>>()}
                                {
                                    let actions = row_actions.get_value();
                                    let has_actions_cell = !actions.is_empty();
                                    let row_id = ctx_row_id.get_value();
                                    let inline_actions: Vec<RowAction> = actions.iter().filter(|a| a.inline).cloned().collect();
                                    let menu_actions: Vec<RowAction> = actions.iter().filter(|a| !a.inline).cloned().collect();
                                    let has_menu = !menu_actions.is_empty();
                                    let inline_views = inline_actions.into_iter().map(|action| {
                                        let rid = row_id.clone();
                                        view! {
                                            <button type="button"
                                                data-rs-datatable-action=action.id
                                                data-rs-row-id=rid
                                                data-rs-datatable-inline-action=""
                                                class={if action.danger { "danger".to_string() } else { String::new() }}
                                            >
                                                {action.label}
                                            </button>
                                        }
                                    }).collect::<Vec<_>>();
                                    let menu_views = menu_actions.into_iter().map(|action| {
                                        let rid = row_id.clone();
                                        view! {
                                            <DropdownMenuItem
                                                class={if action.danger { "danger".to_string() } else { String::new() }}
                                            >
                                                <span data-rs-datatable-action=action.id data-rs-row-id=rid>
                                                    {action.label}
                                                </span>
                                            </DropdownMenuItem>
                                        }
                                    }).collect::<Vec<_>>();
                                    view! {
                                        <td data-rs-datatable-cell="" data-rs-col-actions="" class=if has_actions_cell { "" } else { "rs-col-hidden" }>
                                            <div data-rs-datatable-actions-cell="">
                                                <div data-rs-datatable-inline-actions="">{inline_views}</div>
                                                <div data-rs-datatable-menu-actions="" hidden=(!has_menu)>
                                                    <DropdownMenu>{menu_views}</DropdownMenu>
                                                </div>
                                            </div>
                                        </td>
                                    }
                                }
                            </DataTableRowPrimitive>
                        };

                        view! {
                            <>
                                {main_row}
                                {expand_content.map(|content| {
                                    let rid = real_id.get_value();
                                    view! {
                                        <DataTableExpandRowPrimitive row_id=rid colspan=col_count.to_string()>
                                            {content}
                                        </DataTableExpandRowPrimitive>
                                    }
                                })}
                            </>
                        }
                    }).collect::<Vec<_>>()}
                </DataTableBodyPrimitive>
            </DataTableTablePrimitive></ScrollArea>

            <div data-rs-datatable-context-menus="">
                {visible_data.get_value().into_iter().map(|(idx, row)| {
                    let has_actions = !row_actions.get_value().is_empty();
                    let ctx_actions = row_actions.get_value();
                    let real_id2 = row_id_fn.get_value().as_ref().map(|f| f(&row)).unwrap_or_else(|| idx.to_string());
                    let ctx_row_id2 = real_id2.clone();
                    view! {
                        <div data-rs-datatable-row-context="" data-rs-context-menu=""
                            data-rs-row-id=ctx_row_id2 hidden=(!has_actions)>
                            <ContextMenuContent>
                                {ctx_actions.into_iter().map(|action| {
                                    let rid2 = real_id2.clone();
                                    view! {
                                        <ContextMenuItem>
                                            <span data-rs-datatable-action=action.id data-rs-row-id=rid2>
                                                {action.label}
                                            </span>
                                        </ContextMenuItem>
                                    }
                                }).collect::<Vec<_>>()}
                            </ContextMenuContent>
                        </div>
                    }
                }).collect::<Vec<_>>()}
            </div>

            <DataTableEmptyPrimitive class="hidden".to_string()>
                "No results found."
            </DataTableEmptyPrimitive>
            <div data-rs-datatable-pagination="">
                <button type="button" data-rs-action="prev" data-rs-datatable-pagination-btn="" disabled=true>
                    "Previous"
                </button>
                <span data-rs-pagination-info="">{format!("1 of {}", total_pages)}</span>
                <button type="button" data-rs-action="next" data-rs-datatable-pagination-btn="" disabled={total_pages <= 1}>
                    "Next"
                </button>
            </div>
        </DataTablePrimitive>
    }
}