Compare commits

...

2 Commits

Author SHA1 Message Date
085ce51b0a ... 2025-08-08 08:32:23 +02:00
7d3ddc12ed ... 2025-08-08 08:30:20 +02:00
8 changed files with 1023 additions and 89 deletions

View File

@ -12,7 +12,10 @@ categories = ["gui", "wasm", "web-programming"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
yew = { version="0.21", features=["csr"] }
yew-router = "0.18"
web-sys = { version = "0.3", features = ["Document", "HtmlElement", "Window"] }
gloo-utils = "0.1"
gloo-storage = "0.2"
gloo-net = "0.4"
wasm-bindgen-futures = "0.4"
serde = { version = "1.0", features = ["derive"] }

View File

@ -251,4 +251,294 @@ body {
background: none !important;
color: black !important;
}
}
// Kanban Board Styles
.kanban-board {
background-color: var(--bs-body-bg);
min-height: 100vh;
padding: 2rem 0;
.kanban-header {
margin-bottom: 2rem;
text-align: center;
h1 {
color: var(--bs-body-color);
font-weight: 700;
margin-bottom: 0.5rem;
}
.kanban-description {
color: var(--bs-navbar-color);
font-size: 1.1rem;
}
}
.kanban-columns {
display: flex;
gap: 1.5rem;
overflow-x: auto;
padding: 1rem 0;
min-height: 70vh;
@media (max-width: 768px) {
flex-direction: column;
gap: 1rem;
}
}
.kanban-column {
flex: 1;
min-width: 300px;
background-color: var(--bs-feature-bg);
border-radius: 0.75rem;
padding: 1.5rem;
box-shadow: var(--bs-shadow);
border: 1px solid var(--bs-card-border);
.column-header {
margin-bottom: 1.5rem;
padding-bottom: 1rem;
border-bottom: 2px solid var(--bs-card-border);
.column-title {
font-size: 1.25rem;
font-weight: 600;
color: var(--bs-body-color);
margin-bottom: 0.5rem;
display: flex;
align-items: center;
gap: 0.5rem;
.card-count {
background-color: var(--bs-navbar-color);
color: var(--bs-body-bg);
border-radius: 50%;
width: 1.5rem;
height: 1.5rem;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
font-weight: 700;
}
}
.column-description {
color: var(--bs-navbar-color);
font-size: 0.9rem;
margin: 0;
}
}
.column-cards {
display: flex;
flex-direction: column;
gap: 1rem;
min-height: 200px;
}
}
.kanban-card {
background-color: var(--bs-card-bg);
border: 1px solid var(--bs-card-border);
border-radius: 0.5rem;
padding: 1rem;
box-shadow: var(--bs-shadow);
transition: all 0.3s ease;
cursor: pointer;
&:hover {
transform: translateY(-2px);
box-shadow: var(--bs-shadow-lg);
}
.card-header {
margin-bottom: 0.75rem;
.card-title {
font-size: 1rem;
font-weight: 600;
color: var(--bs-body-color);
margin-bottom: 0.5rem;
line-height: 1.3;
}
.card-description {
color: var(--bs-navbar-color);
font-size: 0.875rem;
line-height: 1.4;
margin: 0;
}
}
.card-meta {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-bottom: 0.75rem;
.priority-badge {
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
&.priority-high {
background-color: rgba(220, 53, 69, 0.1);
color: #dc3545;
border: 1px solid rgba(220, 53, 69, 0.2);
}
&.priority-medium {
background-color: rgba(255, 193, 7, 0.1);
color: #ffc107;
border: 1px solid rgba(255, 193, 7, 0.2);
}
&.priority-low {
background-color: rgba(25, 135, 84, 0.1);
color: #198754;
border: 1px solid rgba(25, 135, 84, 0.2);
}
}
.due-date {
background-color: rgba(var(--bs-primary-rgb), 0.1);
color: var(--bs-primary);
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-size: 0.75rem;
font-weight: 500;
border: 1px solid rgba(var(--bs-primary-rgb), 0.2);
}
}
.card-tags {
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
margin-bottom: 0.75rem;
.tag {
background-color: var(--bs-navbar-color);
color: var(--bs-body-bg);
padding: 0.125rem 0.5rem;
border-radius: 1rem;
font-size: 0.7rem;
font-weight: 500;
opacity: 0.8;
}
}
.card-assignee {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.75rem;
color: var(--bs-navbar-color);
font-size: 0.875rem;
.assignee-avatar {
width: 1.5rem;
height: 1.5rem;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 0.7rem;
font-weight: 600;
}
}
.card-stats {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 0.75rem;
border-top: 1px solid var(--bs-card-border);
.stat-item {
display: flex;
align-items: center;
gap: 0.25rem;
color: var(--bs-navbar-color);
font-size: 0.75rem;
i {
font-size: 0.875rem;
}
}
.checklist-progress {
display: flex;
align-items: center;
gap: 0.5rem;
.progress-bar {
width: 40px;
height: 4px;
background-color: var(--bs-card-border);
border-radius: 2px;
overflow: hidden;
.progress-fill {
height: 100%;
background-color: #198754;
transition: width 0.3s ease;
}
}
.progress-text {
font-size: 0.7rem;
color: var(--bs-navbar-color);
}
}
}
}
}
// Responsive kanban adjustments
@media (max-width: 992px) {
.kanban-board {
.kanban-columns {
gap: 1rem;
}
.kanban-column {
min-width: 280px;
padding: 1rem;
}
}
}
@media (max-width: 576px) {
.kanban-board {
padding: 1rem 0;
.kanban-column {
min-width: 100%;
margin: 0;
}
.kanban-card {
padding: 0.75rem;
.card-meta {
flex-direction: column;
align-items: flex-start;
gap: 0.25rem;
}
.card-stats {
flex-direction: column;
align-items: flex-start;
gap: 0.5rem;
}
}
}
}

139
kanban_data.json Normal file
View File

@ -0,0 +1,139 @@
{
"title": "Project Management Board",
"description": "Track project progress with this kanban board",
"columns": [
{
"id": "todo",
"title": "To Do",
"description": "Tasks that need to be started",
"cards": [
{
"id": "card-1",
"title": "Design User Interface",
"description": "Create wireframes and mockups for the new feature",
"priority": "high",
"assignee": "Alice Johnson",
"tags": ["design", "ui/ux"],
"dueDate": "2024-01-15",
"attachments": 2,
"comments": 3,
"checklist": {
"completed": 1,
"total": 4
}
},
{
"id": "card-2",
"title": "Research Market Trends",
"description": "Analyze current market trends and competitor analysis",
"priority": "medium",
"assignee": "Bob Smith",
"tags": ["research", "analysis"],
"dueDate": "2024-01-20",
"attachments": 0,
"comments": 1,
"checklist": {
"completed": 0,
"total": 3
}
}
]
},
{
"id": "in-progress",
"title": "In Progress",
"description": "Tasks currently being worked on",
"cards": [
{
"id": "card-3",
"title": "Implement Authentication",
"description": "Set up user authentication system with JWT tokens and secure password handling",
"priority": "high",
"assignee": "Charlie Brown",
"tags": ["backend", "security"],
"dueDate": "2024-01-12",
"attachments": 1,
"comments": 5,
"checklist": {
"completed": 2,
"total": 5
}
},
{
"id": "card-4",
"title": "Database Migration",
"description": "Migrate existing data to new database schema",
"priority": "medium",
"assignee": "Diana Prince",
"tags": ["database", "migration"],
"dueDate": "2024-01-18",
"attachments": 3,
"comments": 2,
"checklist": {
"completed": 3,
"total": 6
}
}
]
},
{
"id": "review",
"title": "Review",
"description": "Tasks pending review and approval",
"cards": [
{
"id": "card-5",
"title": "API Documentation",
"description": "Complete API documentation with examples and usage guidelines",
"priority": "low",
"assignee": "Eve Wilson",
"tags": ["documentation", "api"],
"dueDate": "2024-01-10",
"attachments": 2,
"comments": 4,
"checklist": {
"completed": 4,
"total": 4
}
}
]
},
{
"id": "done",
"title": "Done",
"description": "Completed tasks",
"cards": [
{
"id": "card-6",
"title": "Setup Development Environment",
"description": "Configure development tools and environment for the team",
"priority": "high",
"assignee": "Frank Miller",
"tags": ["setup", "devops"],
"dueDate": "2024-01-05",
"attachments": 1,
"comments": 2,
"checklist": {
"completed": 3,
"total": 3
}
},
{
"id": "card-7",
"title": "Initial Project Planning",
"description": "Define project scope, timeline, and resource allocation",
"priority": "high",
"assignee": "Grace Lee",
"tags": ["planning", "management"],
"dueDate": "2024-01-03",
"attachments": 4,
"comments": 8,
"checklist": {
"completed": 5,
"total": 5
}
}
]
}
]
}

139
public/kanban_data.json Normal file
View File

@ -0,0 +1,139 @@
{
"title": "Project Management Board",
"description": "Track project progress with this kanban board",
"columns": [
{
"id": "todo",
"title": "To Do",
"description": "Tasks that need to be started",
"cards": [
{
"id": "card-1",
"title": "Design User Interface",
"description": "Create wireframes and mockups for the new feature",
"priority": "high",
"assignee": "Alice Johnson",
"tags": ["design", "ui/ux"],
"dueDate": "2024-01-15",
"attachments": 2,
"comments": 3,
"checklist": {
"completed": 1,
"total": 4
}
},
{
"id": "card-2",
"title": "Research Market Trends",
"description": "Analyze current market trends and competitor analysis",
"priority": "medium",
"assignee": "Bob Smith",
"tags": ["research", "analysis"],
"dueDate": "2024-01-20",
"attachments": 0,
"comments": 1,
"checklist": {
"completed": 0,
"total": 3
}
}
]
},
{
"id": "in-progress",
"title": "In Progress",
"description": "Tasks currently being worked on",
"cards": [
{
"id": "card-3",
"title": "Implement Authentication",
"description": "Set up user authentication system with JWT tokens and secure password handling",
"priority": "high",
"assignee": "Charlie Brown",
"tags": ["backend", "security"],
"dueDate": "2024-01-12",
"attachments": 1,
"comments": 5,
"checklist": {
"completed": 2,
"total": 5
}
},
{
"id": "card-4",
"title": "Database Migration",
"description": "Migrate existing data to new database schema",
"priority": "medium",
"assignee": "Diana Prince",
"tags": ["database", "migration"],
"dueDate": "2024-01-18",
"attachments": 3,
"comments": 2,
"checklist": {
"completed": 3,
"total": 6
}
}
]
},
{
"id": "review",
"title": "Review",
"description": "Tasks pending review and approval",
"cards": [
{
"id": "card-5",
"title": "API Documentation",
"description": "Complete API documentation with examples and usage guidelines",
"priority": "low",
"assignee": "Eve Wilson",
"tags": ["documentation", "api"],
"dueDate": "2024-01-10",
"attachments": 2,
"comments": 4,
"checklist": {
"completed": 4,
"total": 4
}
}
]
},
{
"id": "done",
"title": "Done",
"description": "Completed tasks",
"cards": [
{
"id": "card-6",
"title": "Setup Development Environment",
"description": "Configure development tools and environment for the team",
"priority": "high",
"assignee": "Frank Miller",
"tags": ["setup", "devops"],
"dueDate": "2024-01-05",
"attachments": 1,
"comments": 2,
"checklist": {
"completed": 3,
"total": 3
}
},
{
"id": "card-7",
"title": "Initial Project Planning",
"description": "Define project scope, timeline, and resource allocation",
"priority": "high",
"assignee": "Grace Lee",
"tags": ["planning", "management"],
"dueDate": "2024-01-03",
"attachments": 4,
"comments": 8,
"checklist": {
"completed": 5,
"total": 5
}
}
]
}
]
}

View File

@ -1,7 +1,10 @@
use yew::prelude::*;
use yew_router::prelude::*;
use gloo_utils::document;
use gloo_storage::{LocalStorage, Storage};
use serde::{Deserialize, Serialize};
use crate::home::HomePage;
use crate::kanban::KanbanBoard;
#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize)]
enum Theme {
@ -18,6 +21,25 @@ impl Theme {
}
}
#[derive(Clone, Routable, PartialEq)]
enum Route {
#[at("/")]
Home,
#[at("/kanban")]
Kanban,
#[not_found]
#[at("/404")]
NotFound,
}
fn switch(routes: Route) -> Html {
match routes {
Route::Home => html! { <HomePage /> },
Route::Kanban => html! { <KanbanBoard /> },
Route::NotFound => html! { <h1>{ "404 - Page not found" }</h1> },
}
}
#[function_component(App)]
pub fn app() -> Html {
let theme = use_state(|| {
@ -41,19 +63,26 @@ pub fn app() -> Html {
let navbar_class = if *theme == Theme::Dark { "navbar navbar-expand-lg navbar-dark" } else { "navbar navbar-expand-lg navbar-light" };
html! {
<>
<BrowserRouter>
<nav class={navbar_class}>
<div class="container-fluid">
<a class="navbar-brand fw-bold" href="#">{"🦀 Yew Bootstrap App"}</a>
<Link<Route> to={Route::Home} classes="navbar-brand fw-bold">
{"🦀 Yew Bootstrap App"}
</Link<Route>>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="#">
<Link<Route> to={Route::Home} classes="nav-link">
<i class="bi bi-house-fill me-1"></i>{"Home"}
</a>
</Link<Route>>
</li>
<li class="nav-item">
<Link<Route> to={Route::Kanban} classes="nav-link">
<i class="bi bi-kanban me-1"></i>{"Kanban"}
</Link<Route>>
</li>
<li class="nav-item">
<a class="nav-link" href="#">
@ -80,90 +109,7 @@ pub fn app() -> Html {
</div>
</nav>
<div class="hero-section jumbotron">
<div class="container py-5 text-center">
<div class="row justify-content-center">
<div class="col-lg-8">
<h1 class="display-4 fw-bold mb-4">{"🚀 Welcome to Yew Bootstrap!"}</h1>
<p class="lead fs-5 mb-4">{"Experience the power of Rust and WebAssembly with this modern Yew application featuring Bootstrap 5 integration, responsive design, and seamless dark mode switching."}</p>
<div class="d-grid gap-2 d-md-flex justify-content-md-center">
<button class="btn btn-primary btn-lg px-4 me-md-2" type="button">
<i class="bi bi-rocket-takeoff me-2"></i>{"Get Started"}
</button>
<button class="btn btn-outline-secondary btn-lg px-4" type="button">
<i class="bi bi-github me-2"></i>{"View Source"}
</button>
</div>
</div>
</div>
</div>
</div>
<div class="container my-5">
<div class="row g-4">
<div class="col-md-4">
<div class="card h-100 shadow-sm">
<div class="card-body text-center">
<div class="feature-icon bg-primary bg-gradient text-white rounded-3 mb-3 mx-auto" style="width: 4rem; height: 4rem; display: flex; align-items: center; justify-content: center;">
<i class="bi bi-lightning-charge fs-2"></i>
</div>
<h3 class="card-title">{"⚡ Fast Performance"}</h3>
<p class="card-text">{"Built with Rust and WebAssembly for blazing fast performance. Experience near-native speed in your web applications."}</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card h-100 shadow-sm">
<div class="card-body text-center">
<div class="feature-icon bg-success bg-gradient text-white rounded-3 mb-3 mx-auto" style="width: 4rem; height: 4rem; display: flex; align-items: center; justify-content: center;">
<i class="bi bi-shield-check fs-2"></i>
</div>
<h3 class="card-title">{"🛡️ Type Safety"}</h3>
<p class="card-text">{"Rust's powerful type system ensures memory safety and prevents common programming errors at compile time."}</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card h-100 shadow-sm">
<div class="card-body text-center">
<div class="feature-icon bg-info bg-gradient text-white rounded-3 mb-3 mx-auto" style="width: 4rem; height: 4rem; display: flex; align-items: center; justify-content: center;">
<i class="bi bi-phone fs-2"></i>
</div>
<h3 class="card-title">{"📱 Responsive Design"}</h3>
<p class="card-text">{"Fully responsive design that works perfectly on desktop, tablet, and mobile devices with Bootstrap 5."}</p>
</div>
</div>
</div>
</div>
</div>
<div class="bg-body-secondary py-5">
<div class="container">
<div class="row align-items-center">
<div class="col-lg-6">
<h2 class="display-6 fw-bold mb-3">{"🎨 Modern UI Components"}</h2>
<p class="lead">{"This application showcases modern UI patterns with smooth theme transitions, interactive components, and beautiful typography."}</p>
<ul class="list-unstyled">
<li class="mb-2"><i class="bi bi-check-circle-fill text-success me-2"></i>{"Dark/Light theme switching"}</li>
<li class="mb-2"><i class="bi bi-check-circle-fill text-success me-2"></i>{"Responsive navigation"}</li>
<li class="mb-2"><i class="bi bi-check-circle-fill text-success me-2"></i>{"Bootstrap 5 integration"}</li>
<li class="mb-2"><i class="bi bi-check-circle-fill text-success me-2"></i>{"Custom CSS properties"}</li>
</ul>
</div>
<div class="col-lg-6 text-center">
<div class="p-4">
<i class="bi bi-palette2 display-1 text-primary"></i>
</div>
</div>
</div>
</div>
</div>
<footer class="bg-body-tertiary py-4 mt-5">
<div class="container text-center">
<p class="mb-0">{"Built with ❤️ using Yew, Rust, and Bootstrap 5"}</p>
</div>
</footer>
</>
<Switch<Route> render={switch} />
</BrowserRouter>
}
}

94
src/home.rs Normal file
View File

@ -0,0 +1,94 @@
use yew::prelude::*;
#[function_component(HomePage)]
pub fn home_page() -> Html {
html! {
<>
<div class="hero-section jumbotron">
<div class="container py-5 text-center">
<div class="row justify-content-center">
<div class="col-lg-8">
<h1 class="display-4 fw-bold mb-4">{"🚀 Welcome to Yew Bootstrap!"}</h1>
<p class="lead fs-5 mb-4">{"Experience the power of Rust and WebAssembly with this modern Yew application featuring Bootstrap 5 integration, responsive design, and seamless dark mode switching."}</p>
<div class="d-grid gap-2 d-md-flex justify-content-md-center">
<button class="btn btn-primary btn-lg px-4 me-md-2" type="button">
<i class="bi bi-rocket-takeoff me-2"></i>{"Get Started"}
</button>
<button class="btn btn-outline-secondary btn-lg px-4" type="button">
<i class="bi bi-github me-2"></i>{"View Source"}
</button>
</div>
</div>
</div>
</div>
</div>
<div class="container my-5">
<div class="row g-4">
<div class="col-md-4">
<div class="card h-100 shadow-sm">
<div class="card-body text-center">
<div class="feature-icon bg-primary bg-gradient text-white rounded-3 mb-3 mx-auto" style="width: 4rem; height: 4rem; display: flex; align-items: center; justify-content: center;">
<i class="bi bi-lightning-charge fs-2"></i>
</div>
<h3 class="card-title">{"⚡ Fast Performance"}</h3>
<p class="card-text">{"Built with Rust and WebAssembly for blazing fast performance. Experience near-native speed in your web applications."}</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card h-100 shadow-sm">
<div class="card-body text-center">
<div class="feature-icon bg-success bg-gradient text-white rounded-3 mb-3 mx-auto" style="width: 4rem; height: 4rem; display: flex; align-items: center; justify-content: center;">
<i class="bi bi-shield-check fs-2"></i>
</div>
<h3 class="card-title">{"🛡️ Type Safety"}</h3>
<p class="card-text">{"Rust's powerful type system ensures memory safety and prevents common programming errors at compile time."}</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card h-100 shadow-sm">
<div class="card-body text-center">
<div class="feature-icon bg-info bg-gradient text-white rounded-3 mb-3 mx-auto" style="width: 4rem; height: 4rem; display: flex; align-items: center; justify-content: center;">
<i class="bi bi-phone fs-2"></i>
</div>
<h3 class="card-title">{"📱 Responsive Design"}</h3>
<p class="card-text">{"Fully responsive design that works perfectly on desktop, tablet, and mobile devices with Bootstrap 5."}</p>
</div>
</div>
</div>
</div>
</div>
<div class="bg-body-secondary py-5">
<div class="container">
<div class="row align-items-center">
<div class="col-lg-6">
<h2 class="display-6 fw-bold mb-3">{"🎨 Modern UI Components"}</h2>
<p class="lead">{"This application showcases modern UI patterns with smooth theme transitions, interactive components, and beautiful typography."}</p>
<ul class="list-unstyled">
<li class="mb-2"><i class="bi bi-check-circle-fill text-success me-2"></i>{"Dark/Light theme switching"}</li>
<li class="mb-2"><i class="bi bi-check-circle-fill text-success me-2"></i>{"Responsive navigation"}</li>
<li class="mb-2"><i class="bi bi-check-circle-fill text-success me-2"></i>{"Bootstrap 5 integration"}</li>
<li class="mb-2"><i class="bi bi-check-circle-fill text-success me-2"></i>{"Custom CSS properties"}</li>
<li class="mb-2"><i class="bi bi-check-circle-fill text-success me-2"></i>{"Kanban board with rich content"}</li>
</ul>
</div>
<div class="col-lg-6 text-center">
<div class="p-4">
<i class="bi bi-palette2 display-1 text-primary"></i>
</div>
</div>
</div>
</div>
</div>
<footer class="bg-body-tertiary py-4 mt-5">
<div class="container text-center">
<p class="mb-0">{"Built with ❤️ using Yew, Rust, and Bootstrap 5"}</p>
</div>
</footer>
</>
}
}

321
src/kanban.rs Normal file
View File

@ -0,0 +1,321 @@
use yew::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct KanbanData {
pub title: String,
pub description: String,
pub columns: Vec<KanbanColumn>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct KanbanColumn {
pub id: String,
pub title: String,
pub description: String,
pub cards: Vec<KanbanCard>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct KanbanCard {
pub id: String,
pub title: String,
pub description: String,
pub priority: String,
pub assignee: String,
pub tags: Vec<String>,
#[serde(rename = "dueDate")]
pub due_date: String,
pub attachments: u32,
pub comments: u32,
pub checklist: ChecklistInfo,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ChecklistInfo {
pub completed: u32,
pub total: u32,
}
fn get_sample_data() -> KanbanData {
KanbanData {
title: "Project Management Board".to_string(),
description: "Track project progress with this kanban board".to_string(),
columns: vec![
KanbanColumn {
id: "todo".to_string(),
title: "To Do".to_string(),
description: "Tasks that need to be started".to_string(),
cards: vec![
KanbanCard {
id: "card-1".to_string(),
title: "Design User Interface".to_string(),
description: "Create wireframes and mockups for the new feature".to_string(),
priority: "high".to_string(),
assignee: "Alice Johnson".to_string(),
tags: vec!["design".to_string(), "ui/ux".to_string()],
due_date: "2024-01-15".to_string(),
attachments: 2,
comments: 3,
checklist: ChecklistInfo { completed: 1, total: 4 },
},
KanbanCard {
id: "card-2".to_string(),
title: "Research Market Trends".to_string(),
description: "Analyze current market trends and competitor analysis".to_string(),
priority: "medium".to_string(),
assignee: "Bob Smith".to_string(),
tags: vec!["research".to_string(), "analysis".to_string()],
due_date: "2024-01-20".to_string(),
attachments: 0,
comments: 1,
checklist: ChecklistInfo { completed: 0, total: 3 },
},
],
},
KanbanColumn {
id: "in-progress".to_string(),
title: "In Progress".to_string(),
description: "Tasks currently being worked on".to_string(),
cards: vec![
KanbanCard {
id: "card-3".to_string(),
title: "Implement Authentication".to_string(),
description: "Set up user authentication system with JWT tokens and secure password handling".to_string(),
priority: "high".to_string(),
assignee: "Charlie Brown".to_string(),
tags: vec!["backend".to_string(), "security".to_string()],
due_date: "2024-01-12".to_string(),
attachments: 1,
comments: 5,
checklist: ChecklistInfo { completed: 2, total: 5 },
},
KanbanCard {
id: "card-4".to_string(),
title: "Database Migration".to_string(),
description: "Migrate existing data to new database schema".to_string(),
priority: "medium".to_string(),
assignee: "Diana Prince".to_string(),
tags: vec!["database".to_string(), "migration".to_string()],
due_date: "2024-01-18".to_string(),
attachments: 3,
comments: 2,
checklist: ChecklistInfo { completed: 3, total: 6 },
},
],
},
KanbanColumn {
id: "review".to_string(),
title: "Review".to_string(),
description: "Tasks pending review and approval".to_string(),
cards: vec![
KanbanCard {
id: "card-5".to_string(),
title: "API Documentation".to_string(),
description: "Complete API documentation with examples and usage guidelines".to_string(),
priority: "low".to_string(),
assignee: "Eve Wilson".to_string(),
tags: vec!["documentation".to_string(), "api".to_string()],
due_date: "2024-01-10".to_string(),
attachments: 2,
comments: 4,
checklist: ChecklistInfo { completed: 4, total: 4 },
},
],
},
KanbanColumn {
id: "done".to_string(),
title: "Done".to_string(),
description: "Completed tasks".to_string(),
cards: vec![
KanbanCard {
id: "card-6".to_string(),
title: "Setup Development Environment".to_string(),
description: "Configure development tools and environment for the team".to_string(),
priority: "high".to_string(),
assignee: "Frank Miller".to_string(),
tags: vec!["setup".to_string(), "devops".to_string()],
due_date: "2024-01-05".to_string(),
attachments: 1,
comments: 2,
checklist: ChecklistInfo { completed: 3, total: 3 },
},
KanbanCard {
id: "card-7".to_string(),
title: "Initial Project Planning".to_string(),
description: "Define project scope, timeline, and resource allocation".to_string(),
priority: "high".to_string(),
assignee: "Grace Lee".to_string(),
tags: vec!["planning".to_string(), "management".to_string()],
due_date: "2024-01-03".to_string(),
attachments: 4,
comments: 8,
checklist: ChecklistInfo { completed: 5, total: 5 },
},
],
},
],
}
}
#[function_component(KanbanBoard)]
pub fn kanban_board() -> Html {
let data = get_sample_data();
html! {
<div class="kanban-board">
<div class="container-fluid">
<div class="kanban-header">
<h1>{&data.title}</h1>
<p class="kanban-description">{&data.description}</p>
</div>
<div class="kanban-columns">
{for data.columns.iter().map(|column| {
html! {
<KanbanColumnComponent column={column.clone()} />
}
})}
</div>
</div>
</div>
}
}
#[derive(Properties, PartialEq)]
pub struct KanbanColumnProps {
pub column: KanbanColumn,
}
#[function_component(KanbanColumnComponent)]
pub fn kanban_column_component(props: &KanbanColumnProps) -> Html {
let column = &props.column;
html! {
<div class="kanban-column">
<div class="column-header">
<div class="column-title">
{&column.title}
<span class="card-count">{column.cards.len()}</span>
</div>
<p class="column-description">{&column.description}</p>
</div>
<div class="column-cards">
{for column.cards.iter().map(|card| {
html! {
<KanbanCardComponent card={card.clone()} />
}
})}
</div>
</div>
}
}
#[derive(Properties, PartialEq)]
pub struct KanbanCardProps {
pub card: KanbanCard,
}
#[function_component(KanbanCardComponent)]
pub fn kanban_card_component(props: &KanbanCardProps) -> Html {
let card = &props.card;
let priority_class = match card.priority.as_str() {
"high" => "priority-high",
"medium" => "priority-medium",
"low" => "priority-low",
_ => "priority-medium",
};
let progress_percentage = if card.checklist.total > 0 {
(card.checklist.completed as f32 / card.checklist.total as f32 * 100.0) as u32
} else {
0
};
let assignee_initials = card.assignee
.split_whitespace()
.map(|word| word.chars().next().unwrap_or(' '))
.collect::<String>()
.to_uppercase();
html! {
<div class="kanban-card">
<div class="card-header">
<h3 class="card-title">{&card.title}</h3>
<p class="card-description">{&card.description}</p>
</div>
<div class="card-meta">
<span class={classes!("priority-badge", priority_class)}>
{&card.priority}
</span>
<span class="due-date">
<i class="bi bi-calendar-event me-1"></i>
{&card.due_date}
</span>
</div>
{if !card.tags.is_empty() {
html! {
<div class="card-tags">
{for card.tags.iter().map(|tag| {
html! {
<span class="tag">{tag}</span>
}
})}
</div>
}
} else {
html! {}
}}
<div class="card-assignee">
<div class="assignee-avatar">
{assignee_initials}
</div>
<span>{&card.assignee}</span>
</div>
<div class="card-stats">
<div class="d-flex gap-3">
{if card.attachments > 0 {
html! {
<div class="stat-item">
<i class="bi bi-paperclip"></i>
<span>{card.attachments}</span>
</div>
}
} else {
html! {}
}}
{if card.comments > 0 {
html! {
<div class="stat-item">
<i class="bi bi-chat-dots"></i>
<span>{card.comments}</span>
</div>
}
} else {
html! {}
}}
</div>
{if card.checklist.total > 0 {
html! {
<div class="checklist-progress">
<div class="progress-bar">
<div class="progress-fill" style={format!("width: {}%", progress_percentage)}></div>
</div>
<span class="progress-text">
{format!("{}/{}", card.checklist.completed, card.checklist.total)}
</span>
</div>
}
} else {
html! {}
}}
</div>
</div>
}
}

View File

@ -1,4 +1,6 @@
mod app;
mod home;
mod kanban;
use app::App;
fn main() {