Skip to content

Instantly share code, notes, and snippets.

@francis-codex
Last active July 21, 2025 03:15
Show Gist options
  • Save francis-codex/1f461d0c27059dab9358a7ff62668a24 to your computer and use it in GitHub Desktop.
Save francis-codex/1f461d0c27059dab9358a7ff62668a24 to your computer and use it in GitHub Desktop.
A Rust-based command-line application for managing sales leads with progressive implementation stages.

Sales Lead Tracker

A Rust-based command-line application for managing sales leads with progressive implementation stages.

Features

  • Add Leads: Store lead information (name, contact, value, status)
  • Display Leads: View all leads in formatted table
  • Remove Leads: Delete leads by ID
  • Edit Leads: Modify existing lead details
  • Cancel Edits: Revert changes during editing
  • Data Validation: Input validation and error handling

Implementation Stages

Stage 1: Basic Operations (Vec)

  • Add leads to vector storage
  • Display all leads
  • Simple 3-option menu

Stage 2: Advanced Storage (HashMap)

  • Migrated from Vec to HashMap for better performance
  • Added remove functionality
  • ID-based lead lookup

Stage 3: Full CRUD Operations

  • Edit existing leads
  • Cancel edit operations with backup/restore
  • Complete lead lifecycle management

Project Structure

src/
├── main.rs              # Main application with switchable implementations
├── mods.rs            # Data structures (Leads struct, LeadStatus enum)
├── hashmapfn.rs        # HashMap operations
└── vecfn.rs    # Vec operations

Installation & Setup

  1. Clone Repository

    git clone <repository-url>
    cd sales-lead-tracker
  2. Install Rust (if not installed)

    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
  3. Run Application

    cargo run

Switching Between Implementations

Current: HashMap Implementation

  • Full functionality with remove/edit features
  • Uses HashMap for efficient ID-based operations

Switch to Vec Implementation

In src/main.rs:

  1. Comment out HashMap section
  2. Uncomment Vec section
  3. Run cargo run

Data Models

struct Leads {
    id: u32,
    name: String,
    contact: String,
    value: f64,
    status: LeadStatus,
}

enum LeadStatus {
    New,
    Contacted,
    Qualified,
    Lost,
    Converted,
}

Usage Examples

Adding a Lead

Enter lead name: John Doe
Enter contact information: [email protected]
Enter lead value: 5000.00
Enter lead status: New

Display Output

ID    Name                 Contact         Value      Status    
-----------------------------------------------------------------
1     John Doe             [email protected]  $5000.00   New
2     Jane Smith           [email protected]  $3500.00   Contacted

Editing a Lead

1. Edit Name  2. Edit Contact  3. Edit Value  4. Save

Implementation Details

Stage 1 → Stage 2 Migration

  • From: Vec<Leads> with linear search
  • To: HashMap<u32, Leads> with direct ID access
  • Benefits: O(1) lookup, better scalability, efficient remove operations

Menu Interface

=== Lead Management System ===
1. Add Lead
2. Display Leads
3. Remove Lead
4. Edit Lead
5. Cancel Edit
6. Exit
===================================

Learning Outcomes

  • Rust ownership and borrowing concepts
  • Data structure selection and migration
  • Error handling patterns
  • Modular programming design
  • Command-line interface development
  • Progressive software development methodology

use std::collections::HashMap;
use std::io;
use crate::mods::{Leads, LeadStatus};
pub fn add_lead(leads: &mut HashMap<u32, Leads>, name: String, contact: String, value: f64, status: LeadStatus) {
if name.is_empty() || contact.is_empty() || value <= 0.0 {
println!("Invalid lead data provided.");
return;
}
let id = leads.len() as u32 + 1;
let new_lead = Leads { id, name, contact, value, status };
leads.insert(id, new_lead);
println!("Lead added successfully.");
}
pub fn display_leads(leads: &HashMap<u32, Leads>) {
if leads.is_empty() {
println!("No leads available.");
return;
}
println!("{:<5} {:<20} {:<15} {:<10} {:<10}", "ID", "Name", "Contact", "Value", "Status");
println!("{:-<65}", "");
let mut sorted_leads: Vec<_> = leads.iter().collect();
sorted_leads.sort_by_key(|(id, _)| *id);
for (_, lead) in sorted_leads {
println!("{:<5} {:<20} {:<15} ${:<9.2} {:?}",
lead.id, lead.name, lead.contact, lead.value, lead.status);
}
}
pub fn remove_lead(leads: &mut HashMap<u32, Leads>) {
if leads.is_empty() {
println!("No leads available to remove.");
return;
}
display_leads(leads);
println!("\nEnter lead ID to remove:");
let mut id_input = String::new();
io::stdin().read_line(&mut id_input).expect("Failed to read line");
if let Ok(id) = id_input.trim().parse::<u32>() {
if leads.remove(&id).is_some() {
println!("Lead removed successfully.");
} else {
println!("Lead not found.");
}
} else {
println!("Invalid ID.");
}
}
pub fn edit_lead(leads: &mut HashMap<u32, Leads>, backup: &mut Option<Leads>) {
if leads.is_empty() {
println!("No leads available to edit.");
return;
}
display_leads(leads);
println!("\nEnter lead ID to edit:");
let mut id_input = String::new();
io::stdin().read_line(&mut id_input).expect("Failed to read line");
if let Ok(id) = id_input.trim().parse::<u32>() {
if let Some(lead) = leads.get_mut(&id) {
*backup = Some(lead.clone());
println!("Editing lead: {}", lead.name);
println!("1. Edit Name 2. Edit Contact 3. Edit Value 4. Save");
loop {
let mut choice = String::new();
io::stdin().read_line(&mut choice).expect("Failed to read line");
match choice.trim() {
"1" => {
println!("Enter new name:");
let mut new_name = String::new();
io::stdin().read_line(&mut new_name).expect("Failed to read line");
lead.name = new_name.trim().to_string();
},
"2" => {
println!("Enter new contact:");
let mut new_contact = String::new();
io::stdin().read_line(&mut new_contact).expect("Failed to read line");
lead.contact = new_contact.trim().to_string();
},
"3" => {
println!("Enter new value:");
let mut new_value = String::new();
io::stdin().read_line(&mut new_value).expect("Failed to read line");
if let Ok(value) = new_value.trim().parse::<f64>() {
lead.value = value;
}
},
"4" => {
*backup = None;
println!("Changes saved.");
break;
},
_ => println!("Invalid choice."),
}
}
} else {
println!("Lead not found.");
}
} else {
println!("Invalid ID.");
}
}
pub fn cancel_edit(leads: &mut HashMap<u32, Leads>, backup: &mut Option<Leads>) {
if let Some(original) = backup.take() {
leads.insert(original.id, original);
println!("Changes cancelled.");
} else {
println!("No changes to cancel.");
}
}
pub fn get_lead_input() -> Result<(String, String, f64, LeadStatus), String> {
let mut name = String::new();
let mut contact = String::new();
let mut value_str = String::new();
let mut status_str = String::new();
println!("Enter lead name:");
io::stdin().read_line(&mut name).expect("Failed to read line");
println!("Enter contact information:");
io::stdin().read_line(&mut contact).expect("Failed to read line");
println!("Enter lead value:");
io::stdin().read_line(&mut value_str).expect("Failed to read line");
println!("Enter lead status (New, Contacted, Qualified, Lost, Converted):");
io::stdin().read_line(&mut status_str).expect("Failed to read line");
let value: f64 = value_str.trim().parse()
.map_err(|_| "Invalid value entered.")?;
let status = match status_str.trim().to_lowercase().as_str() {
"new" => LeadStatus::New,
"contacted" => LeadStatus::Contacted,
"qualified" => LeadStatus::Qualified,
"lost" => LeadStatus::Lost,
"converted" => LeadStatus::Converted,
_ => return Err("Invalid status entered.".to_string()),
};
Ok((name.trim().to_string(), contact.trim().to_string(), value, status))
}
mod mods;
// ... HASHMAP IMPLEMENTATION ...
mod hashmapfn;
mod vecfn;
use std::collections::HashMap;
use std::io;
use mods::Leads;
use hashmapfn::*;
use vecfn::*;
fn main() {
let mut leads: HashMap<u32, Leads> = HashMap::new();
let mut backup_lead: Option<Leads> = None;
let mut input = String::new();
loop {
println!("... Lead Management System (HashMap) ...");
println!("1. Add Lead");
println!("2. Display Leads");
println!("3. Remove Lead");
println!("4. Edit Lead");
println!("5. Cancel Edit");
println!("6. Exit");
println!("....................................................");
println!("Please select an option:");
input.clear();
io::stdin().read_line(&mut input).expect("Failed to read line");
match input.trim() {
"1" => {
match get_lead_input() {
Ok((name, contact, value, status)) => {
add_lead(&mut leads, name, contact, value, status);
},
Err(err) => {
println!("{}", err);
}
}
},
"2" => display_leads(&leads),
"3" => remove_lead(&mut leads),
"4" => edit_lead(&mut leads, &mut backup_lead),
"5" => cancel_edit(&mut leads, &mut backup_lead),
"6" => {
println!("Goodbye!");
break;
},
_ => println!("Invalid option selected. Please try again."),
}
}
}
// ... VEC IMPLEMENTATION ...
/*
mod vec_operations;
use std::io;
use models::Leads;
use vec_operations::*;
fn main() {
let mut leads: Vec<Leads> = Vec::new();
let mut input = String::new();
loop {
println!("... Lead Management System (Vec) ...");
println!("1. Add Lead");
println!("2. Display Leads");
println!("3. Exit");
println!(".....................................................");
println!("Please select an option:");
input.clear();
io::stdin().read_line(&mut input).expect("Failed to read line");
match input.trim() {
"1" => {
match get_lead_input() {
Ok((name, contact, value, status)) => {
add_lead(&mut leads, name, contact, value, status);
},
Err(err) => {
println!("{}", err);
}
}
},
"2" => display_leads(&leads),
"3" => {
println!("Goodbye!");
break;
},
_ => println!("Invalid option selected. Please try again."),
}
}
}
*/
#[derive(Debug, Clone)]
pub struct Leads {
pub id: u32,
pub name: String,
pub contact: String,
pub value: f64,
pub status: LeadStatus,
}
#[derive(Debug, Clone)]
pub enum LeadStatus {
New,
Contacted,
Qualified,
Lost,
Converted,
}
// use std::io;
// use crate::mods::{Leads, LeadStatus};
// pub fn add_lead(leads: &mut Vec<Leads>, name: String, contact: String, value: f64, status: LeadStatus) {
// if name.is_empty() || contact.is_empty() || value <= 0.0 {
// println!("Invalid lead data provided.");
// return;
// }
// let id = leads.len() as u32 + 1;
// let new_lead = Leads { id, name, contact, value, status };
// leads.push(new_lead);
// println!("Lead added successfully.");
// }
// pub fn display_leads(leads: &Vec<Leads>) {
// if leads.is_empty() {
// println!("No leads available.");
// return;
// }
// println!("{:<5} {:<20} {:<15} {:<10} {:<10}", "ID", "Name", "Contact", "Value", "Status");
// println!("{:-<65}", "");
// for lead in leads {
// println!("{:<5} {:<20} {:<15} ${:<9.2} {:?}",
// lead.id, lead.name, lead.contact, lead.value, lead.status);
// }
// }
// pub fn get_lead_input() -> Result<(String, String, f64, LeadStatus), String> {
// let mut name = String::new();
// let mut contact = String::new();
// let mut value_str = String::new();
// let mut status_str = String::new();
// println!("Enter lead name:");
// io::stdin().read_line(&mut name).expect("Failed to read line");
// println!("Enter contact information:");
// io::stdin().read_line(&mut contact).expect("Failed to read line");
// println!("Enter lead value:");
// io::stdin().read_line(&mut value_str).expect("Failed to read line");
// println!("Enter lead status (New, Contacted, Qualified, Lost, Converted):");
// io::stdin().read_line(&mut status_str).expect("Failed to read line");
// let value: f64 = value_str.trim().parse()
// .map_err(|_| "Invalid value entered.")?;
// let status = match status_str.trim().to_lowercase().as_str() {
// "new" => LeadStatus::New,
// "contacted" => LeadStatus::Contacted,
// "qualified" => LeadStatus::Qualified,
// "lost" => LeadStatus::Lost,
// "converted" => LeadStatus::Converted,
// _ => return Err("Invalid status entered.".to_string()),
// };
// Ok((name.trim().to_string(), contact.trim().to_string(), value, status))
// }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment