qorzen_oxide/ui/pages/
plugins.rs

1// src/ui/pages/plugins.rs - Plugin management and marketplace
2
3use dioxus::prelude::*;
4#[allow(unused_imports)]
5use dioxus_router::prelude::*;
6
7#[allow(unused_imports)]
8use crate::ui::{
9    pages::{EmptyState, PageWrapper},
10    router::Route,
11    state::use_app_state,
12};
13
14/// Main plugins page component
15#[component]
16pub fn Plugins() -> Element {
17    let mut active_tab = use_signal(|| "installed".to_string());
18    let mut search_query = use_signal(String::new);
19    let mut loading = use_signal(|| false);
20
21    // Mock plugin data
22    let installed_plugins = get_installed_plugins();
23    let available_plugins = get_available_plugins();
24
25    rsx! {
26        PageWrapper {
27            title: "Plugins".to_string(),
28            subtitle: Some("Extend your application with plugins".to_string()),
29            actions: Some(rsx! {
30                div {
31                    class: "flex space-x-3",
32                    button {
33                        r#type: "button",
34                        class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500",
35                        onclick: move |_| {
36                            loading.set(true);
37                            spawn(async move {
38                                #[cfg(not(target_arch = "wasm32"))]
39                                tokio::time::sleep(std::time::Duration::from_millis(1000)).await;
40                                #[cfg(target_arch = "wasm32")]
41                                gloo_timers::future::TimeoutFuture::new(1000).await;
42                                loading.set(false);
43                            });
44                        },
45                        if loading() {
46                            svg {
47                                class: "animate-spin -ml-1 mr-2 h-4 w-4",
48                                xmlns: "http://www.w3.org/2000/svg",
49                                fill: "none",
50                                view_box: "0 0 24 24",
51                                circle {
52                                    class: "opacity-25",
53                                    cx: "12",
54                                    cy: "12",
55                                    r: "10",
56                                    stroke: "currentColor",
57                                    stroke_width: "4"
58                                }
59                                path {
60                                    class: "opacity-75",
61                                    fill: "currentColor",
62                                    d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
63                                }
64                            }
65                        } else {
66                            svg {
67                                class: "-ml-1 mr-2 h-4 w-4",
68                                xmlns: "http://www.w3.org/2000/svg",
69                                fill: "none",
70                                view_box: "0 0 24 24",
71                                stroke: "currentColor",
72                                path {
73                                    stroke_linecap: "round",
74                                    stroke_linejoin: "round",
75                                    stroke_width: "2",
76                                    d: "M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
77                                }
78                            }
79                        }
80                        "Refresh"
81                    }
82                    button {
83                        r#type: "button",
84                        class: "inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500",
85                        svg {
86                            class: "-ml-1 mr-2 h-4 w-4",
87                            xmlns: "http://www.w3.org/2000/svg",
88                            fill: "none",
89                            view_box: "0 0 24 24",
90                            stroke: "currentColor",
91                            path {
92                                stroke_linecap: "round",
93                                stroke_linejoin: "round",
94                                stroke_width: "2",
95                                d: "M12 6v6m0 0v6m0-6h6m-6 0H6"
96                            }
97                        }
98                        "Install Plugin"
99                    }
100                }
101            }),
102
103            // Search and filters
104            div {
105                class: "mb-6",
106                div {
107                    class: "relative",
108                    input {
109                        r#type: "text",
110                        placeholder: "Search plugins...",
111                        class: "block w-full pr-12 border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500 sm:text-sm",
112                        value: "{search_query}",
113                        oninput: move |e| search_query.set(e.value())
114                    }
115                    div {
116                        class: "absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none",
117                        svg {
118                            class: "h-5 w-5 text-gray-400",
119                            xmlns: "http://www.w3.org/2000/svg",
120                            view_box: "0 0 20 20",
121                            fill: "currentColor",
122                            path {
123                                fill_rule: "evenodd",
124                                d: "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z",
125                                clip_rule: "evenodd"
126                            }
127                        }
128                    }
129                }
130            }
131
132            // Tabs
133            div {
134                class: "border-b border-gray-200 mb-6",
135                nav {
136                    class: "-mb-px flex space-x-8",
137                    button {
138                        r#type: "button",
139                        class: format!(
140                            "py-2 px-1 border-b-2 font-medium text-sm {}",
141                            if active_tab() == "installed" {
142                                "border-blue-500 text-blue-600"
143                            } else {
144                                "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
145                            }
146                        ),
147                        onclick: move |_| active_tab.set("installed".to_string()),
148                        "Installed ({installed_plugins.len()})"
149                    }
150                    button {
151                        r#type: "button",
152                        class: format!(
153                            "py-2 px-1 border-b-2 font-medium text-sm {}",
154                            if active_tab() == "available" {
155                                "border-blue-500 text-blue-600"
156                            } else {
157                                "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
158                            }
159                        ),
160                        onclick: move |_| active_tab.set("available".to_string()),
161                        "Available ({available_plugins.len()})"
162                    }
163                    button {
164                        r#type: "button",
165                        class: format!(
166                            "py-2 px-1 border-b-2 font-medium text-sm {}",
167                            if active_tab() == "updates" {
168                                "border-blue-500 text-blue-600"
169                            } else {
170                                "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
171                            }
172                        ),
173                        onclick: move |_| active_tab.set("updates".to_string()),
174                        "Updates (2)"
175                    }
176                }
177            }
178
179            // Tab content
180            match active_tab().as_str() {
181                "installed" => rsx! {
182                    InstalledPluginsTab {
183                        plugins: installed_plugins,
184                        search_query: search_query()
185                    }
186                },
187                "available" => rsx! {
188                    AvailablePluginsTab {
189                        plugins: available_plugins,
190                        search_query: search_query()
191                    }
192                },
193                "updates" => rsx! {
194                    UpdatesTab {}
195                },
196                _ => rsx! { div { "Unknown tab" } }
197            }
198        }
199    }
200}
201
202/// Installed plugins tab
203#[component]
204fn InstalledPluginsTab(plugins: Vec<PluginInfo>, search_query: String) -> Element {
205    let filtered_plugins: Vec<PluginInfo> = plugins
206        .into_iter()
207        .filter(|p| {
208            if search_query.is_empty() {
209                true
210            } else {
211                p.name.to_lowercase().contains(&search_query.to_lowercase())
212                    || p.description
213                        .to_lowercase()
214                        .contains(&search_query.to_lowercase())
215            }
216        })
217        .collect();
218
219    rsx! {
220        if filtered_plugins.is_empty() {
221            EmptyState {
222                icon: "🧩".to_string(),
223                title: if search_query.is_empty() {
224                    "No plugins installed".to_string()
225                } else {
226                    "No plugins found".to_string()
227                },
228                description: if search_query.is_empty() {
229                    "Install plugins to extend your application functionality".to_string()
230                } else {
231                    "Try adjusting your search terms".to_string()
232                },
233            }
234        } else {
235            div {
236                class: "grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3",
237                for plugin in filtered_plugins {
238                    PluginCard {
239                        key: "{plugin.id}",
240                        plugin: plugin.clone(),
241                        is_installed: true
242                    }
243                }
244            }
245        }
246    }
247}
248
249/// Available plugins tab
250#[component]
251fn AvailablePluginsTab(plugins: Vec<PluginInfo>, search_query: String) -> Element {
252    let filtered_plugins: Vec<PluginInfo> = plugins
253        .into_iter()
254        .filter(|p| {
255            if search_query.is_empty() {
256                true
257            } else {
258                p.name.to_lowercase().contains(&search_query.to_lowercase())
259                    || p.description
260                        .to_lowercase()
261                        .contains(&search_query.to_lowercase())
262            }
263        })
264        .collect();
265
266    rsx! {
267        if filtered_plugins.is_empty() {
268            EmptyState {
269                icon: "🔍".to_string(),
270                title: "No plugins found".to_string(),
271                description: "Try adjusting your search terms".to_string(),
272            }
273        } else {
274            div {
275                class: "grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3",
276                for plugin in filtered_plugins {
277                    PluginCard {
278                        key: "{plugin.id}",
279                        plugin: plugin.clone(),
280                        is_installed: false
281                    }
282                }
283            }
284        }
285    }
286}
287
288/// Updates tab
289#[component]
290fn UpdatesTab() -> Element {
291    let updates = get_plugin_updates();
292
293    rsx! {
294        if updates.is_empty() {
295            EmptyState {
296                icon: "✅".to_string(),
297                title: "All plugins up to date".to_string(),
298                description: "Your plugins are running the latest versions".to_string(),
299            }
300        } else {
301            div {
302                class: "space-y-4",
303                for update in updates {
304                    UpdateCard {
305                        key: "{update.plugin_id}",
306                        update: update.clone()
307                    }
308                }
309            }
310        }
311    }
312}
313
314/// Individual plugin card component
315#[component]
316fn PluginCard(plugin: PluginInfo, is_installed: bool) -> Element {
317    let mut installing = use_signal(|| false);
318    let mut uninstalling = use_signal(|| false);
319
320    let handle_install = {
321        move |_| {
322            installing.set(true);
323            spawn(async move {
324                #[cfg(not(target_arch = "wasm32"))]
325                tokio::time::sleep(std::time::Duration::from_millis(2000)).await;
326                #[cfg(target_arch = "wasm32")]
327                gloo_timers::future::TimeoutFuture::new(2000).await;
328                installing.set(false);
329            });
330        }
331    };
332
333    let handle_uninstall = {
334        move |_| {
335            uninstalling.set(true);
336            spawn(async move {
337                #[cfg(not(target_arch = "wasm32"))]
338                tokio::time::sleep(std::time::Duration::from_millis(1500)).await;
339                #[cfg(target_arch = "wasm32")]
340                gloo_timers::future::TimeoutFuture::new(1500).await;
341                uninstalling.set(false);
342            });
343        }
344    };
345
346    rsx! {
347        div {
348            class: "bg-white overflow-hidden shadow rounded-lg hover:shadow-md transition-shadow",
349            div {
350                class: "p-6",
351                div {
352                    class: "flex items-center justify-between",
353                    div {
354                        class: "flex items-center",
355                        div {
356                            class: "flex-shrink-0",
357                            span {
358                                class: "text-3xl",
359                                "{plugin.icon}"
360                            }
361                        }
362                        div {
363                            class: "ml-4",
364                            h3 {
365                                class: "text-lg font-medium text-gray-900",
366                                "{plugin.name}"
367                            }
368                            p {
369                                class: "text-sm text-gray-500",
370                                "v{plugin.version} by {plugin.author}"
371                            }
372                        }
373                    }
374                    div {
375                        class: "flex-shrink-0",
376                        if is_installed {
377                            span {
378                                class: "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800",
379                                "Installed"
380                            }
381                        } else {
382                            span {
383                                class: "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800",
384                                "Available"
385                            }
386                        }
387                    }
388                }
389
390                div {
391                    class: "mt-4",
392                    p {
393                        class: "text-sm text-gray-600",
394                        "{plugin.description}"
395                    }
396                }
397
398                // Plugin stats
399                div {
400                    class: "mt-4 flex items-center text-xs text-gray-500 space-x-4",
401                    div {
402                        class: "flex items-center",
403                        svg {
404                            class: "h-4 w-4 mr-1",
405                            xmlns: "http://www.w3.org/2000/svg",
406                            view_box: "0 0 20 20",
407                            fill: "currentColor",
408                            path {
409                                d: "M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"
410                            }
411                        }
412                        "{plugin.rating}/5"
413                    }
414                    div {
415                        class: "flex items-center",
416                        svg {
417                            class: "h-4 w-4 mr-1",
418                            xmlns: "http://www.w3.org/2000/svg",
419                            view_box: "0 0 20 20",
420                            fill: "currentColor",
421                            path {
422                                fill_rule: "evenodd",
423                                d: "M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm3.293-7.707a1 1 0 011.414 0L9 10.586V3a1 1 0 112 0v7.586l1.293-1.293a1 1 0 111.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z",
424                                clip_rule: "evenodd"
425                            }
426                        }
427                        "{plugin.downloads}"
428                    }
429                    div {
430                        class: "flex items-center",
431                        svg {
432                            class: "h-4 w-4 mr-1",
433                            xmlns: "http://www.w3.org/2000/svg",
434                            view_box: "0 0 20 20",
435                            fill: "currentColor",
436                            path {
437                                fill_rule: "evenodd",
438                                d: "M12.316 3.051a1 1 0 01.633 1.265l-4 12a1 1 0 11-1.898-.632l4-12a1 1 0 011.265-.633zM5.707 6.293a1 1 0 010 1.414L3.414 10l2.293 2.293a1 1 0 11-1.414 1.414l-3-3a1 1 0 010-1.414l3-3a1 1 0 011.414 0zm8.586 0a1 1 0 011.414 0l3 3a1 1 0 010 1.414l-3 3a1 1 0 11-1.414-1.414L16.586 10l-2.293-2.293a1 1 0 010-1.414z",
439                                clip_rule: "evenodd"
440                            }
441                        }
442                        "{plugin.category}"
443                    }
444                }
445
446                // Action buttons
447                div {
448                    class: "mt-6 flex space-x-3",
449                    if is_installed {
450                        Link {
451                            to: Route::Plugin { plugin_id: plugin.id.clone() },
452                            class: "flex-1 bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 text-center",
453                            "Configure"
454                        }
455                        button {
456                            r#type: "button",
457                            class: "flex-1 bg-red-600 py-2 px-3 border border-transparent rounded-md shadow-sm text-sm leading-4 font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 disabled:opacity-50",
458                            disabled: uninstalling(),
459                            onclick: handle_uninstall,
460                            if uninstalling() { "Uninstalling..." } else { "Uninstall" }
461                        }
462                    } else {
463                        button {
464                            r#type: "button",
465                            class: "flex-1 bg-blue-600 py-2 px-3 border border-transparent rounded-md shadow-sm text-sm leading-4 font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50",
466                            disabled: installing(),
467                            onclick: handle_install,
468                            if installing() { "Installing..." } else { "Install" }
469                        }
470                        button {
471                            r#type: "button",
472                            class: "bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500",
473                            "Details"
474                        }
475                    }
476                }
477            }
478        }
479    }
480}
481
482/// Update card component
483#[component]
484fn UpdateCard(update: PluginUpdate) -> Element {
485    let mut updating = use_signal(|| false);
486
487    let handle_update = {
488        move |_| {
489            updating.set(true);
490            spawn(async move {
491                #[cfg(not(target_arch = "wasm32"))]
492                tokio::time::sleep(std::time::Duration::from_millis(2000)).await;
493                #[cfg(target_arch = "wasm32")]
494                gloo_timers::future::TimeoutFuture::new(2000).await;
495                updating.set(false);
496            });
497        }
498    };
499
500    rsx! {
501        div {
502            class: "bg-white overflow-hidden shadow rounded-lg border-l-4 border-yellow-400",
503            div {
504                class: "p-6",
505                div {
506                    class: "flex items-center justify-between",
507                    div {
508                        class: "flex items-center",
509                        div {
510                            class: "flex-shrink-0",
511                            span {
512                                class: "text-2xl",
513                                "{update.icon}"
514                            }
515                        }
516                        div {
517                            class: "ml-4",
518                            h3 {
519                                class: "text-lg font-medium text-gray-900",
520                                "{update.name}"
521                            }
522                            p {
523                                class: "text-sm text-gray-500",
524                                "Update available: v{update.current_version} → v{update.new_version}"
525                            }
526                        }
527                    }
528                    div {
529                        class: "flex space-x-3",
530                        button {
531                            r#type: "button",
532                            class: "bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500",
533                            "View Changes"
534                        }
535                        button {
536                            r#type: "button",
537                            class: "bg-yellow-600 py-2 px-3 border border-transparent rounded-md shadow-sm text-sm leading-4 font-medium text-white hover:bg-yellow-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-yellow-500 disabled:opacity-50",
538                            disabled: updating(),
539                            onclick: handle_update,
540                            if updating() { "Updating..." } else { "Update" }
541                        }
542                    }
543                }
544
545                if !update.changelog.is_empty() {
546                    div {
547                        class: "mt-4",
548                        h4 {
549                            class: "text-sm font-medium text-gray-900 mb-2",
550                            "What's New:"
551                        }
552                        ul {
553                            class: "text-sm text-gray-600 space-y-1",
554                            for item in &update.changelog {
555                                li {
556                                    class: "flex items-start",
557                                    span {
558                                        class: "text-green-500 mr-2",
559                                        "•"
560                                    }
561                                    "{item}"
562                                }
563                            }
564                        }
565                    }
566                }
567            }
568        }
569    }
570}
571
572/// Plugin view component for individual plugin pages
573#[component]
574pub fn PluginView(plugin_id: String, #[props(default = None)] page: Option<String>) -> Element {
575    rsx! {
576        PageWrapper {
577            title: format!("Plugin: {}", plugin_id),
578            subtitle: Some("Plugin configuration and management".to_string()),
579
580            div {
581                class: "bg-white shadow rounded-lg p-6",
582                h2 {
583                    class: "text-xl font-semibold text-gray-900 mb-4",
584                    "Plugin: {plugin_id}"
585                }
586
587                if let Some(page_name) = page {
588                    p {
589                        class: "text-gray-600 mb-4",
590                        "Page: {page_name}"
591                    }
592                }
593
594                p {
595                    class: "text-gray-600",
596                    "This would show the plugin's interface and configuration options."
597                }
598
599                div {
600                    class: "mt-6 p-4 bg-blue-50 rounded-md",
601                    p {
602                        class: "text-sm text-blue-800",
603                        "🔌 Plugin content would be rendered here dynamically based on the plugin's configuration."
604                    }
605                }
606            }
607        }
608    }
609}
610
611// Data structures and mock data
612#[derive(Debug, Clone, PartialEq)]
613struct PluginInfo {
614    id: String,
615    name: String,
616    version: String,
617    author: String,
618    description: String,
619    icon: String,
620    rating: f32,
621    downloads: String,
622    category: String,
623}
624
625#[derive(Debug, Clone, PartialEq)]
626struct PluginUpdate {
627    plugin_id: String,
628    name: String,
629    icon: String,
630    current_version: String,
631    new_version: String,
632    changelog: Vec<String>,
633}
634
635fn get_installed_plugins() -> Vec<PluginInfo> {
636    vec![
637        PluginInfo {
638            id: "inventory".to_string(),
639            name: "Inventory Management".to_string(),
640            version: "2.1.0".to_string(),
641            author: "QorzenTech".to_string(),
642            description: "Complete inventory tracking and management system with barcode support"
643                .to_string(),
644            icon: "📦".to_string(),
645            rating: 4.8,
646            downloads: "12.5k".to_string(),
647            category: "Business".to_string(),
648        },
649        PluginInfo {
650            id: "analytics".to_string(),
651            name: "Analytics Dashboard".to_string(),
652            version: "1.5.2".to_string(),
653            author: "DataViz Inc".to_string(),
654            description: "Advanced analytics and reporting with beautiful visualizations"
655                .to_string(),
656            icon: "📊".to_string(),
657            rating: 4.6,
658            downloads: "8.2k".to_string(),
659            category: "Analytics".to_string(),
660        },
661        PluginInfo {
662            id: "backup".to_string(),
663            name: "Backup & Sync".to_string(),
664            version: "3.0.1".to_string(),
665            author: "SecureData".to_string(),
666            description: "Automated backup and synchronization across multiple cloud providers"
667                .to_string(),
668            icon: "☁️".to_string(),
669            rating: 4.9,
670            downloads: "15.1k".to_string(),
671            category: "Utility".to_string(),
672        },
673    ]
674}
675
676fn get_available_plugins() -> Vec<PluginInfo> {
677    vec![
678        PluginInfo {
679            id: "crm".to_string(),
680            name: "Customer Relations".to_string(),
681            version: "1.2.0".to_string(),
682            author: "CRM Solutions".to_string(),
683            description: "Comprehensive customer relationship management system".to_string(),
684            icon: "👥".to_string(),
685            rating: 4.4,
686            downloads: "6.8k".to_string(),
687            category: "Business".to_string(),
688        },
689        PluginInfo {
690            id: "payments".to_string(),
691            name: "Payment Processing".to_string(),
692            version: "2.3.1".to_string(),
693            author: "PayTech".to_string(),
694            description: "Secure payment processing with multiple gateway support".to_string(),
695            icon: "💳".to_string(),
696            rating: 4.7,
697            downloads: "9.4k".to_string(),
698            category: "Finance".to_string(),
699        },
700        PluginInfo {
701            id: "notifications".to_string(),
702            name: "Smart Notifications".to_string(),
703            version: "1.0.5".to_string(),
704            author: "NotifyMe".to_string(),
705            description: "Advanced notification system with email, SMS, and push support"
706                .to_string(),
707            icon: "🔔".to_string(),
708            rating: 4.2,
709            downloads: "3.1k".to_string(),
710            category: "Communication".to_string(),
711        },
712        PluginInfo {
713            id: "scheduler".to_string(),
714            name: "Task Scheduler".to_string(),
715            version: "1.8.0".to_string(),
716            author: "TimeKeeper".to_string(),
717            description: "Powerful task scheduling and automation system".to_string(),
718            icon: "⏰".to_string(),
719            rating: 4.5,
720            downloads: "5.7k".to_string(),
721            category: "Productivity".to_string(),
722        },
723    ]
724}
725
726fn get_plugin_updates() -> Vec<PluginUpdate> {
727    vec![
728        PluginUpdate {
729            plugin_id: "inventory".to_string(),
730            name: "Inventory Management".to_string(),
731            icon: "📦".to_string(),
732            current_version: "2.1.0".to_string(),
733            new_version: "2.2.0".to_string(),
734            changelog: vec![
735                "Added bulk import/export functionality".to_string(),
736                "Improved barcode scanning accuracy".to_string(),
737                "Fixed issue with low stock alerts".to_string(),
738                "Enhanced reporting capabilities".to_string(),
739            ],
740        },
741        PluginUpdate {
742            plugin_id: "analytics".to_string(),
743            name: "Analytics Dashboard".to_string(),
744            icon: "📊".to_string(),
745            current_version: "1.5.2".to_string(),
746            new_version: "1.6.0".to_string(),
747            changelog: vec![
748                "New real-time dashboard widgets".to_string(),
749                "Added data export to Excel".to_string(),
750                "Performance improvements for large datasets".to_string(),
751            ],
752        },
753    ]
754}
755
756#[cfg(test)]
757mod tests {
758    use super::*;
759
760    #[test]
761    fn test_plugins_component_creation() {
762        let _plugins = rsx! { Plugins {} };
763    }
764
765    #[test]
766    fn test_plugin_view_creation() {
767        let _plugin_view = rsx! {
768            PluginView {
769                plugin_id: "test".to_string(),
770                page: Some("config".to_string())
771            }
772        };
773    }
774
775    #[test]
776    fn test_mock_data() {
777        let installed = get_installed_plugins();
778        let available = get_available_plugins();
779        let updates = get_plugin_updates();
780
781        assert!(!installed.is_empty());
782        assert!(!available.is_empty());
783        assert!(!updates.is_empty());
784    }
785}