diff --git a/templates/project/_detail.go.html b/templates/project/_detail.go.html new file mode 100644 index 0000000000000000000000000000000000000000..5d25f281f199593bb6c822e8ffcf17b5c70c76b0 --- /dev/null +++ b/templates/project/_detail.go.html @@ -0,0 +1,173 @@ +{{ define "project.detail" }} + <div class="container-fluid mt-4"> + + <!-- Project Header Section: Main project information and actions --> + <div class="row mb-4"> + <div class="col"> + <div class="d-flex justify-content-between align-items-start"> + <!-- Left side: Project title and metadata --> + <div> + <!-- Main project name as page heading --> + <h1 class="mb-1">{{ .Data.Project.Name }}</h1> + <!-- Subheading with project ID badge and date range --> + <p class="text-muted mb-0"> + <span class="badge bg-secondary me-2">{{ .Data.Project.ProjectID }}</span> + {{ .Data.Project.StartDate.Format "02.01.2006" }} - {{ .Data.Project.EndDate.Format "02.01.2006" }} + </p> + </div> + + <!-- Right side: Action buttons with dropdown menu --> + <div class="btn-group"> + <!-- Primary edit button using HTMX for seamless navigation --> + <a href="/project/{{ .Data.Project.ID.Hex }}/edit" + hx-boost="true" + hx-target="body" + class="btn btn-outline-primary"> + ✏️ Bearbeiten + </a> + <!-- Dropdown toggle for additional actions --> + <button class="btn btn-outline-secondary dropdown-toggle dropdown-toggle-split" + data-bs-toggle="dropdown"> + <span class="visually-hidden">Toggle Dropdown</span> + </button> + <!-- Dropdown menu with export and delete options --> + <ul class="dropdown-menu"> + <li><a class="dropdown-item" href="#">📄 Export als JSON</a></li> + <li><a class="dropdown-item" href="#">📄 Export als TXT</a></li> + <li><hr class="dropdown-divider"></li> + <li><a class="dropdown-item text-danger" href="#">🗑️ Projekt löschen</a></li> + </ul> + </div> + </div> + </div> + </div> + + <!-- Tab Navigation: Switch between General info and Requirements --> + <ul class="nav nav-tabs mb-4" id="projectTabs" role="tablist"> + <!-- General information tab --> + <li class="nav-item" role="presentation"> + <!-- HTMX loads tab content without full page reload --> + <button class="nav-link {{ if eq .Data.ActiveTab "general" }}active{{ end }}" + hx-get="/project/{{ .Data.Project.ID.Hex }}?tab=general" + hx-target="#tab-content" + hx-push-url="true"> + ℹ️ Allgemein + </button> + </li> + <!-- Requirements tab with count badge --> + <li class="nav-item" role="presentation"> + <button class="nav-link {{ if eq .Data.ActiveTab "requirements" }}active{{ end }}" + hx-get="/project/{{ .Data.Project.ID.Hex }}?tab=requirements" + hx-target="#tab-content" + hx-push-url="true"> + ✅ Anforderungen + <!-- Show requirements count in badge --> + <span class="badge bg-secondary ms-1">{{ .Data.Project.RequirementCount }}</span> + </button> + </li> + </ul> + + <!-- Tab Content Container: Content changes based on active tab --> + <div id="tab-content"> + <!-- Conditionally render tab content based on ActiveTab value --> + {{ if eq .Data.ActiveTab "general" }} + {{ template "project.detail.general" . }} + {{ else if eq .Data.ActiveTab "requirements" }} + {{ template "project.detail.requirements" . }} + {{ end }} + </div> + </div> +{{ end }} + +<!-- General Tab Template: Detailed project information display --> +{{ define "project.detail.general" }} + <div class="row"> + <!-- Left column: Main project details --> + <div class="col-lg-8"> + <div class="card"> + <div class="card-header"> + <h5 class="mb-0">Projektdetails</h5> + </div> + <div class="card-body"> + <!-- Definition list for structured display of project data --> + <dl class="row"> + <!-- Project ID field --> + <dt class="col-sm-3">Projekt-ID:</dt> + <dd class="col-sm-9">{{ .Data.Project.ProjectID }}</dd> + + <!-- Project name field --> + <dt class="col-sm-3">Name:</dt> + <dd class="col-sm-9">{{ .Data.Project.Name }}</dd> + + <!-- Description with fallback for empty descriptions --> + <dt class="col-sm-3">Beschreibung:</dt> + <dd class="col-sm-9"> + {{ if .Data.Project.Description }} + {{ .Data.Project.Description }} + {{ else }} + <em class="text-muted">Keine Beschreibung verfügbar</em> + {{ end }} + </dd> + + <!-- Project timeline information --> + <dt class="col-sm-3">Zeitraum:</dt> + <dd class="col-sm-9"> + {{ .Data.Project.StartDate.Format "02.01.2006" }} - {{ .Data.Project.EndDate.Format "02.01.2006" }} + </dd> + + <!-- Creation timestamp --> + <dt class="col-sm-3">Erstellt:</dt> + <dd class="col-sm-9">{{ .Data.Project.CreatedAt.Format "02.01.2006 15:04" }}</dd> + + <!-- Last update timestamp (only shown if project was updated) --> + {{ if .Data.Project.UpdatedAt }} + <dt class="col-sm-3">Aktualisiert:</dt> + <dd class="col-sm-9">{{ .Data.Project.UpdatedAt.Format "02.01.2006 15:04" }}</dd> + {{ end }} + </dl> + </div> + </div> + </div> + + <!-- Right column: Project statistics and metrics --> + <div class="col-lg-4"> + <div class="card"> + <div class="card-header"> + <h5 class="mb-0">Statistiken</h5> + </div> + <!-- Statistics card showing requirements count --> + <div class="card-body text-center"> + <h3 class="mb-1">{{ .Data.Project.RequirementCount }}</h3> + <small class="text-muted">Anforderungen</small> + </div> + </div> + </div> + </div> +{{ end }} + +<!-- Requirements Tab Template: Requirements management interface --> +{{ define "project.detail.requirements" }} + <div class="requirements-container"> + <!-- Header with title and add new requirement button --> + <div class="d-flex justify-content-between align-items-center mb-3"> + <div> + <!-- Requirements section title with count --> + <h5 class="mb-0">Anforderungen ({{ .Data.Project.RequirementCount }})</h5> + <small class="text-muted">Verwalten Sie alle Anforderungen für dieses Projekt</small> + </div> + <div> + <!-- Link to EIFFEL system for creating new requirements --> + <!-- Passes project ID as parameter for automatic association --> + <a href="/eiffel?project={{ .Data.Project.ProjectID }}" + class="btn btn-primary" + hx-boost="true" + hx-target="body"> + ➕ Neue Anforderung + </a> + </div> + </div> + + <!-- Include requirements list partial template --> + {{ template "project.requirements.list" . }} + </div> +{{ end }} \ No newline at end of file diff --git a/templates/project/_form.go.html b/templates/project/_form.go.html new file mode 100644 index 0000000000000000000000000000000000000000..4279202de47224332999269f166e86d0a4928669 --- /dev/null +++ b/templates/project/_form.go.html @@ -0,0 +1,135 @@ +{{ define "project.form" }} + <!-- Determine if this is an edit form or create form --> + {{ $isEdit := .Data.Form.IsEditForm }} + <!-- Set the form action URL based on form type --> + {{ $action := "/project/new" }} + {{ if $isEdit }} + {{ $action = printf "/project/%s" .Data.Form.Project.ID.Hex }} + {{ end }} + + <div class="container mt-4"> + <div class="row justify-content-center"> + <!-- Center the form in a responsive column --> + <div class="col-lg-8"> + <div class="card"> + <!-- Card header with dynamic title based on form type --> + <div class="card-header"> + <h4 class="mb-0"> + {{ if $isEdit }} + Projekt bearbeiten + {{ else }} + Neues Projekt erstellen + {{ end }} + </h4> + </div> + + <div class="card-body"> + <!-- Form with different HTTP methods based on operation type --> + <form {{ if $isEdit }} + hx-put="{{ $action }}" + hx-target=".card" + hx-swap="outerHTML" + {{ else }} + method="post" + action="{{ $action }}" + {{ end }}> + + <!-- Success/Error Message Display Section --> + <!-- Success messages (shown after successful operations) --> + {{ range .Data.Successes }} + <div class="alert alert-success">{{ . }}</div> + {{ end }} + <!-- Validation error messages (shown when form validation fails) --> + {{ range .Data.AllValidationErrors }} + <div class="alert alert-danger">{{ .Error }}</div> + {{ end }} + + <!-- Form Fields Row: Project ID and Name --> + <div class="row"> + <!-- Project ID Field (disabled in edit mode) --> + <div class="col-md-6 mb-3"> + <label for="projectId" class="form-label">Projekt-ID *</label> + <input type="text" + class="form-control" + id="projectId" + name="ProjectID" + placeholder="PRJ-2025-XXX" + value="{{ .Data.Form.Project.ProjectID }}" + {{ if $isEdit }}disabled{{ end }} + required> + </div> + + <!-- Project Name Field (always editable) --> + <div class="col-md-6 mb-3"> + <label for="name" class="form-label">Projektname *</label> + <input type="text" + class="form-control" + id="name" + name="Name" + placeholder="HARMONY Mobile" + value="{{ .Data.Form.Project.Name }}" + required> + </div> + </div> + + <!-- Description Field (full width, optional) --> + <div class="mb-3"> + <label for="description" class="form-label">Beschreibung</label> + <textarea class="form-control" + id="description" + name="Description" + rows="3" + placeholder="Entwicklung einer mobilen App zur Anforderungserfassung unterwegs.">{{ .Data.Form.Project.Description }}</textarea> + </div> + + <!-- Date Fields Row: Start and End Date --> + <div class="row"> + <!-- Start Date Field --> + <div class="col-md-6 mb-3"> + <label for="startDate" class="form-label">Startdatum *</label> + <input type="date" + class="form-control" + id="startDate" + name="StartDate" + value="{{ .Data.Form.Project.StartDate.Format "2006-01-02" }}" + required> + </div> + + <!-- End Date Field --> + <div class="col-md-6 mb-3"> + <label for="endDate" class="form-label">Enddatum *</label> + <input type="date" + class="form-control" + id="endDate" + name="EndDate" + value="{{ .Data.Form.Project.EndDate.Format "2006-01-02" }}" + required> + </div> + </div> + + <!-- Form Action Buttons --> + <div class="d-flex justify-content-between"> + <!-- Back to list button (left side) --> + <a href="/project/list" + hx-boost="true" + hx-target="body" + class="btn btn-secondary"> + ⬅️ Zurück zur Übersicht + </a> + + <!-- Submit button (right side, text changes based on form type) --> + <button type="submit" class="btn btn-primary"> + {{ if $isEdit }} + ✅ Änderungen speichern + {{ else }} + ➕ Projekt erstellen + {{ end }} + </button> + </div> + </form> + </div> + </div> + </div> + </div> + </div> +{{ end }} \ No newline at end of file diff --git a/templates/project/_list.go.html b/templates/project/_list.go.html new file mode 100644 index 0000000000000000000000000000000000000000..2bf782ce9efe100d445d4322d11405cfb12a44d6 --- /dev/null +++ b/templates/project/_list.go.html @@ -0,0 +1,150 @@ +{{ define "project.list" }} + <div class="project-list"> + <!-- Check if user has any projects - show empty state if none exist --> + {{ if not .Data.Projects }} + <!-- Empty state: Encourage user to create their first project --> + <div class="alert alert-info text-center"> + <h5>Noch keine Projekte vorhanden</h5> + <p>Erstellen Sie Ihr erstes Projekt, um mit der Anforderungserfassung zu beginnen.</p> + <!-- HTMX-powered button for seamless navigation without page reload --> + <button hx-get="/project/new" + hx-target="body" + hx-boost="true" + class="btn btn-primary"> + Erstes Projekt erstellen + </button> + </div> + {{ else }} + <!-- Projects exist: Display them in a responsive grid layout --> + <div class="row"> + <!-- Loop through each project and create a card for it --> + {{ range .Data.Projects }} + <div class="col-md-6 col-lg-4 mb-4"> + <!-- Bootstrap card component with shadow for visual depth --> + <div class="card h-100 shadow-sm project-card"> + + <!-- Card header with project ID and actions dropdown --> + <div class="card-header d-flex justify-content-between align-items-center"> + <!-- Display human-readable project ID (e.g., PRJ-2025-001) --> + <h6 class="mb-0 text-muted">{{ .ProjectID }}</h6> + + <!-- Bootstrap dropdown for project actions (edit, delete) --> + <div class="dropdown"> + <button class="btn btn-sm btn-outline-secondary dropdown-toggle" + type="button" + data-bs-toggle="dropdown"> + ⋮ + </button> + <ul class="dropdown-menu"> + <li> + <!-- Edit link using HTMX for seamless navigation --> + <a class="dropdown-item" + href="/project/{{ .ID.Hex }}/edit" + hx-boost="true" + hx-target="body"> + ✏️ Bearbeiten + </a> + </li> + <li><hr class="dropdown-divider"></li> + <li> + <!-- Delete button triggers modal for confirmation --> + <button class="dropdown-item text-danger" + data-bs-toggle="modal" + data-bs-target="#deleteModal-{{ .ID.Hex }}"> + 🗑️ Löschen + </button> + </li> + </ul> + </div> + </div> + + <!-- Card body with main project information --> + <div class="card-body"> + <!-- Project name as clickable title leading to detail view --> + <h5 class="card-title"> + <a href="/project/{{ .ID.Hex }}" + hx-boost="true" + hx-target="body" + class="text-decoration-none"> + {{ .Name }} + </a> + </h5> + + <!-- Project description with fallback for empty descriptions --> + <p class="card-text text-muted"> + {{ if .Description }} + {{ .Description }} + {{ else }} + Keine Beschreibung verfügbar + {{ end }} + </p> + + <!-- Project timeline showing start and end dates --> + <div class="d-flex justify-content-between text-small text-muted"> + <span> + 📅 {{ .StartDate.Format "02.01.2006" }} + </span> + <span> + 🏁 {{ .EndDate.Format "02.01.2006" }} + </span> + </div> + </div> + + <!-- Card footer with metadata and status information --> + <div class="card-footer bg-transparent"> + <div class="d-flex justify-content-between align-items-center"> + <!-- Creation date for audit purposes --> + <small class="text-muted"> + Erstellt: {{ .CreatedAt.Format "02.01.2006" }} + </small> + <!-- Requirements count badge (currently hardcoded to 0) --> + <span class="badge bg-secondary"> + 0 Anforderungen + </span> + </div> + </div> + </div> + </div> + + <!-- Delete confirmation modal for each project --> + <!-- Each modal has a unique ID based on the project's MongoDB ObjectID --> + <div class="modal fade" id="deleteModal-{{ .ID.Hex }}" tabindex="-1"> + <div class="modal-dialog"> + <div class="modal-content"> + <!-- Modal header with title and close button --> + <div class="modal-header"> + <h5 class="modal-title">Projekt löschen</h5> + <button type="button" class="btn-close" data-bs-dismiss="modal"></button> + </div> + + <!-- Modal body with confirmation message and warning --> + <div class="modal-body"> + <p>Möchten Sie das Projekt <strong>"{{ .Name }}"</strong> wirklich löschen?</p> + <!-- Warning about cascading deletion of requirements --> + <div class="alert alert-warning"> + ⚠️ <strong>Achtung:</strong> Alle zugehörigen Anforderungen werden ebenfalls gelöscht! + </div> + </div> + + <!-- Modal footer with cancel and confirm buttons --> + <div class="modal-footer"> + <button type="button" class="btn btn-secondary" data-bs-dismiss="modal"> + Abbrechen + </button> + <!-- HTMX DELETE request that updates the project list after deletion --> + <button hx-delete="/project/{{ .ID.Hex }}" + hx-target="#project-list" + hx-swap="outerHTML" + data-bs-dismiss="modal" + class="btn btn-danger"> + Projekt löschen + </button> + </div> + </div> + </div> + </div> + {{ end }} + </div> + {{ end }} + </div> +{{ end }} \ No newline at end of file diff --git a/templates/project/_requirements-list.go.html b/templates/project/_requirements-list.go.html new file mode 100644 index 0000000000000000000000000000000000000000..0a1b2c562acc04d786998bed0cf00754100a2a1a --- /dev/null +++ b/templates/project/_requirements-list.go.html @@ -0,0 +1,92 @@ +{{ define "project.requirements.list" }} + <div class="requirements-list"> + <!-- Check if project has any requirements --> + {{ if not .Data.Project.Requirements }} + <!-- Empty state: No requirements exist yet --> + <div class="alert alert-info text-center"> + <h5>Noch keine Anforderungen</h5> + <p class="text-muted">Erstellen Sie die erste Anforderung für dieses Projekt.</p> + <!-- Link to EIFFEL system for requirement creation --> + <!-- Pre-fills the project ID for automatic association --> + <a href="/eiffel?project={{ .Data.Project.ProjectID }}" + class="btn btn-primary" + hx-boost="true" + hx-target="body"> + ➕ Erste Anforderung erstellen + </a> + </div> + {{ else }} + <!-- Requirements exist: Display them in a table format --> + <div class="table-responsive"> + <table class="table table-hover"> + <!-- Table header with column definitions --> + <thead class="table-light"> + <tr> + <th style="width: 100px;">ID</th> <!-- Requirement identifier --> + <th style="width: 80px;">Status</th> <!-- Current requirement status --> + <th>Anforderung</th> <!-- Requirement text (flexible width) --> + <th style="width: 100px;">Aktionen</th> <!-- Action buttons --> + </tr> + </thead> + <tbody> + <!-- Loop through each requirement and create a table row --> + {{ range .Data.Project.Requirements }} + <!-- Each row has a unique ID for potential JavaScript targeting --> + <tr id="requirement-{{ .ID.Hex }}"> + <!-- Requirement ID Column: Display human-readable identifier --> + <td> + <span class="badge bg-light text-dark"> + {{ .RequirementID }} + </span> + </td> + + <!-- Status Column: Dropdown for changing requirement status --> + <td> + <select class="form-select form-select-sm"> + <!-- Loop through available status options --> + {{ range $.Data.RequirementStatuses }} + <!-- Mark current status as selected --> + <option value="{{ . }}" + {{ if eq . $.Status }}selected{{ end }}> + {{ . }} + </option> + {{ end }} + </select> + </td> + + <!-- Requirement Text Column: Show truncated text with tooltip --> + <td> + <!-- Bootstrap tooltip shows full text on hover --> + <div class="requirement-text" + data-bs-toggle="tooltip" + title="{{ .Requirement }}"> + <!-- Truncate long requirements for table readability --> + {{ if gt (len .Requirement) 80 }} + {{ slice .Requirement 0 80 }}... + {{ else }} + {{ .Requirement }} + {{ end }} + </div> + </td> + + <!-- Actions Column: Buttons for requirement operations --> + <td> + <div class="btn-group btn-group-sm"> + <!-- Clone requirement button --> + <button class="btn btn-outline-secondary" title="Klonen"> + 📋 + </button> + <!-- Delete requirement button --> + <button class="btn btn-outline-danger" title="Löschen"> + 🗑️ + </button> + </div> + </td> + </tr> + {{ end }} + </tbody> + </table> + </div> + {{ end }} + </div> +{{ end }} \ No newline at end of file diff --git a/templates/project/detail-page.go.html b/templates/project/detail-page.go.html new file mode 100644 index 0000000000000000000000000000000000000000..46fd2fe193fdf1f3e18b4946953f927b50d4c70a --- /dev/null +++ b/templates/project/detail-page.go.html @@ -0,0 +1,7 @@ +{{ define "project.detail.page" }} + {{ template "index" . }} +{{ end }} + +{{ define "content" }} + {{ template "project.detail" . }} +{{ end }} diff --git a/templates/project/form-page.go.html b/templates/project/form-page.go.html new file mode 100644 index 0000000000000000000000000000000000000000..b61f864ab923a434feaa80c889358718574a16a0 --- /dev/null +++ b/templates/project/form-page.go.html @@ -0,0 +1,7 @@ +{{ define "project.form.page" }} + {{ template "index" . }} +{{ end }} + +{{ define "content" }} + {{ template "project.form" . }} +{{ end }} diff --git a/templates/project/list-page.go.html b/templates/project/list-page.go.html new file mode 100644 index 0000000000000000000000000000000000000000..9d3e80d09e070c69e26e73f47be63809cd642f2f --- /dev/null +++ b/templates/project/list-page.go.html @@ -0,0 +1,40 @@ +{{ define "project.list.page" }} + {{ template "index" . }} +{{ end }} + +{{ define "content" }} + <div class="project-list-container"> + <div class="project-list-header row mb-4"> + <div class="col-6"> + <h1>Projekt-Sammlung</h1> + <p class="text-muted">Verwalten Sie alle Ihre Projekte und deren Anforderungen an einem Ort.</p> + </div> + <div class="col-3"> + <button hx-get="/project/new" + hx-target="body" + hx-boost="true" + class="btn btn-primary"> + + Neues Projekt + </button> + </div> + <div class="col-3"> + <div class="input-group"> + <input type="search" + class="form-control" + placeholder="Suche nach Projekten..." + hx-post="/project/search" + hx-trigger="input changed delay:300ms, search" + hx-target="#project-list" + name="search"> + <button class="btn btn-outline-secondary" type="button"> + 🔍 + </button> + </div> + </div> + </div> + + <div id="project-list"> + {{ template "project.list" . }} + </div> + </div> +{{ end }}