Add Final Project: Todo-App in C++
This commit is contained in:
332
exercises/final_project/todo_cpp/todo.cpp
Normal file
332
exercises/final_project/todo_cpp/todo.cpp
Normal file
@@ -0,0 +1,332 @@
|
||||
// ============================================
|
||||
// Final-Projekt: Todo-App in C++
|
||||
// Moderne C++17 Implementierung mit STL
|
||||
// ============================================
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <ctime>
|
||||
#include <iomanip>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
|
||||
// ============================================
|
||||
// KLASSE: Todo
|
||||
// ============================================
|
||||
class Todo {
|
||||
private:
|
||||
int id_;
|
||||
std::string title_;
|
||||
std::string description_;
|
||||
bool completed_;
|
||||
std::time_t created_at_;
|
||||
|
||||
public:
|
||||
// Konstruktor
|
||||
Todo(int id, std::string title, std::string description)
|
||||
: id_(id)
|
||||
, title_(std::move(title))
|
||||
, description_(std::move(description))
|
||||
, completed_(false)
|
||||
, created_at_(std::time(nullptr)) {}
|
||||
|
||||
// Getter
|
||||
int id() const { return id_; }
|
||||
const std::string& title() const { return title_; }
|
||||
const std::string& description() const { return description_; }
|
||||
bool completed() const { return completed_; }
|
||||
std::time_t created_at() const { return created_at_; }
|
||||
|
||||
// Setter
|
||||
void toggle_completed() { completed_ = !completed_; }
|
||||
void set_title(const std::string& title) { title_ = title; }
|
||||
void set_description(const std::string& desc) { description_ = desc; }
|
||||
|
||||
// Formatierung für Ausgabe
|
||||
std::string to_string() const {
|
||||
std::stringstream ss;
|
||||
ss << "#" << id_ << " [" << (completed_ ? "✓" : " ") << "] " << title_;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
// Serialisierung für Datei-Speicherung
|
||||
std::string serialize() const {
|
||||
std::stringstream ss;
|
||||
ss << id_ << "|" << title_ << "|" << description_ << "|"
|
||||
<< (completed_ ? "1" : "0") << "|" << created_at_;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
static Todo deserialize(const std::string& line) {
|
||||
std::stringstream ss(line);
|
||||
std::string part;
|
||||
std::vector<std::string> parts;
|
||||
|
||||
while (std::getline(ss, part, '|')) {
|
||||
parts.push_back(part);
|
||||
}
|
||||
|
||||
Todo todo(std::stoi(parts[0]), parts[1], parts[2]);
|
||||
if (parts[3] == "1") todo.toggle_completed();
|
||||
return todo;
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// KLASSE: TodoManager
|
||||
// ============================================
|
||||
class TodoManager {
|
||||
private:
|
||||
std::vector<Todo> todos_;
|
||||
int next_id_ = 1;
|
||||
const std::string filename_ = "todos_cpp.txt";
|
||||
|
||||
public:
|
||||
// CRUD Operationen
|
||||
void add(const std::string& title, const std::string& description) {
|
||||
todos_.emplace_back(next_id_++, title, description);
|
||||
std::cout << "✓ Todo hinzugefügt!\n";
|
||||
}
|
||||
|
||||
void list() const {
|
||||
if (todos_.empty()) {
|
||||
std::cout << "Keine Todos vorhanden.\n";
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "\n=== DEINE TODOS ===\n";
|
||||
for (const auto& todo : todos_) {
|
||||
std::cout << todo.to_string() << "\n";
|
||||
if (!todo.description().empty()) {
|
||||
std::cout << " " << todo.description() << "\n";
|
||||
}
|
||||
}
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
void complete(int id) {
|
||||
auto it = find_by_id(id);
|
||||
if (it != todos_.end()) {
|
||||
it->toggle_completed();
|
||||
std::cout << "✓ Todo #" << id << (it->completed() ? " abgeschlossen" : " wieder geöffnet") << "\n";
|
||||
} else {
|
||||
std::cout << "✗ Todo #" << id << " nicht gefunden!\n";
|
||||
}
|
||||
}
|
||||
|
||||
void remove(int id) {
|
||||
auto it = find_by_id(id);
|
||||
if (it != todos_.end()) {
|
||||
todos_.erase(it);
|
||||
// IDs neu zuweisen
|
||||
for (size_t i = 0; i < todos_.size(); ++i) {
|
||||
// Note: In echtem Code würde man IDs nicht ändern
|
||||
}
|
||||
std::cout << "✓ Todo #" << id << " gelöscht!\n";
|
||||
} else {
|
||||
std::cout << "✗ Todo #" << id << " nicht gefunden!\n";
|
||||
}
|
||||
}
|
||||
|
||||
void edit(int id, const std::string& new_title) {
|
||||
auto it = find_by_id(id);
|
||||
if (it != todos_.end()) {
|
||||
it->set_title(new_title);
|
||||
std::cout << "✓ Todo #" << id << " aktualisiert!\n";
|
||||
} else {
|
||||
std::cout << "✗ Todo #" << id << " nicht gefunden!\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Filter-Funktionen
|
||||
void list_open() const {
|
||||
std::cout << "\n=== OFFENE TODOS ===\n";
|
||||
for (const auto& todo : todos_) {
|
||||
if (!todo.completed()) {
|
||||
std::cout << todo.to_string() << "\n";
|
||||
}
|
||||
}
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
void list_completed() const {
|
||||
std::cout << "\n=== ABGESCHLOSSENE TODOS ===\n";
|
||||
for (const auto& todo : todos_) {
|
||||
if (todo.completed()) {
|
||||
std::cout << todo.to_string() << "\n";
|
||||
}
|
||||
}
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
void search(const std::string& keyword) const {
|
||||
std::cout << "\n=== SUCHERGEBNISSE ===\n";
|
||||
for (const auto& todo : todos_) {
|
||||
if (todo.title().find(keyword) != std::string::npos ||
|
||||
todo.description().find(keyword) != std::string::npos) {
|
||||
std::cout << todo.to_string() << "\n";
|
||||
}
|
||||
}
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
// Datei-Operationen
|
||||
void save() const {
|
||||
std::ofstream file(filename_);
|
||||
if (!file) {
|
||||
std::cout << "✗ Fehler beim Speichern!\n";
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& todo : todos_) {
|
||||
file << todo.serialize() << "\n";
|
||||
}
|
||||
|
||||
std::cout << "✓ " << todos_.size() << " Todos gespeichert!\n";
|
||||
}
|
||||
|
||||
void load() {
|
||||
std::ifstream file(filename_);
|
||||
if (!file) {
|
||||
std::cout << "Keine vorhandenen Daten. Starte neu...\n";
|
||||
return;
|
||||
}
|
||||
|
||||
todos_.clear();
|
||||
std::string line;
|
||||
while (std::getline(file, line)) {
|
||||
if (!line.empty()) {
|
||||
todos_.push_back(Todo::deserialize(line));
|
||||
}
|
||||
}
|
||||
|
||||
// next_id aktualisieren
|
||||
if (!todos_.empty()) {
|
||||
next_id_ = std::max_element(todos_.begin(), todos_.end(),
|
||||
[](const Todo& a, const Todo& b) { return a.id() < b.id(); })->id() + 1;
|
||||
}
|
||||
|
||||
std::cout << "✓ " << todos_.size() << " Todos geladen!\n";
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<Todo>::iterator find_by_id(int id) {
|
||||
return std::find_if(todos_.begin(), todos_.end(),
|
||||
[id](const Todo& t) { return t.id() == id; });
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// UI-FUNKTIONEN
|
||||
// ============================================
|
||||
void print_menu() {
|
||||
std::cout << "\n=== TODO-APP (C++ Edition) ===\n";
|
||||
std::cout << "1. Todo hinzufügen\n";
|
||||
std::cout << "2. Alle Todos anzeigen\n";
|
||||
std::cout << "3. Offene Todos anzeigen\n";
|
||||
std::cout << "4. Abgeschlossene Todos anzeigen\n";
|
||||
std::cout << "5. Todo abschließen/öffnen\n";
|
||||
std::cout << "6. Todo bearbeiten\n";
|
||||
std::cout << "7. Todo löschen\n";
|
||||
std::cout << "8. Suchen\n";
|
||||
std::cout << "9. Speichern\n";
|
||||
std::cout << "0. Beenden\n";
|
||||
std::cout << "Wahl: ";
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// HAUPTPROGRAMM
|
||||
// ============================================
|
||||
int main() {
|
||||
TodoManager manager;
|
||||
manager.load();
|
||||
|
||||
std::cout << "\n📝 Willkommen zur Todo-App (C++17)!\n";
|
||||
|
||||
int choice;
|
||||
std::string title, description, keyword;
|
||||
int id;
|
||||
|
||||
while (true) {
|
||||
print_menu();
|
||||
|
||||
if (!(std::cin >> choice)) {
|
||||
std::cin.clear();
|
||||
std::cin.ignore(10000, '\n');
|
||||
std::cout << "Ungültige Eingabe!\n";
|
||||
continue;
|
||||
}
|
||||
std::cin.ignore(10000, '\n'); // Rest der Zeile ignorieren
|
||||
|
||||
switch (choice) {
|
||||
case 1:
|
||||
std::cout << "Titel: ";
|
||||
std::getline(std::cin, title);
|
||||
std::cout << "Beschreibung: ";
|
||||
std::getline(std::cin, description);
|
||||
manager.add(title, description);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
manager.list();
|
||||
break;
|
||||
|
||||
case 3:
|
||||
manager.list_open();
|
||||
break;
|
||||
|
||||
case 4:
|
||||
manager.list_completed();
|
||||
break;
|
||||
|
||||
case 5:
|
||||
std::cout << "Todo-ID: ";
|
||||
std::cin >> id;
|
||||
manager.complete(id);
|
||||
break;
|
||||
|
||||
case 6:
|
||||
std::cout << "Todo-ID: ";
|
||||
std::cin >> id;
|
||||
std::cin.ignore(10000, '\n');
|
||||
std::cout << "Neuer Titel: ";
|
||||
std::getline(std::cin, title);
|
||||
manager.edit(id, title);
|
||||
break;
|
||||
|
||||
case 7:
|
||||
std::cout << "Todo-ID: ";
|
||||
std::cin >> id;
|
||||
manager.remove(id);
|
||||
break;
|
||||
|
||||
case 8:
|
||||
std::cout << "Suchbegriff: ";
|
||||
std::getline(std::cin, keyword);
|
||||
manager.search(keyword);
|
||||
break;
|
||||
|
||||
case 9:
|
||||
manager.save();
|
||||
break;
|
||||
|
||||
case 0: {
|
||||
std::cout << "Speichern vor Beenden? (j/n): ";
|
||||
char save_choice;
|
||||
std::cin >> save_choice;
|
||||
if (save_choice == 'j' || save_choice == 'J') {
|
||||
manager.save();
|
||||
}
|
||||
std::cout << "Auf Wiedersehen!\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
default:
|
||||
std::cout << "Ungültige Wahl!\n";
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user