mirror of
https://github.com/perstarkse/minne.git
synced 2026-04-25 10:18:38 +02:00
feat: modal component, edit functionality
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -36,7 +36,10 @@ use zettle_db::{
|
|||||||
gdpr::{accept_gdpr, deny_gdpr},
|
gdpr::{accept_gdpr, deny_gdpr},
|
||||||
index::{delete_job, delete_text_content, index_handler},
|
index::{delete_job, delete_text_content, index_handler},
|
||||||
ingress_form::{hide_ingress_form, process_ingress_form, show_ingress_form},
|
ingress_form::{hide_ingress_form, process_ingress_form, show_ingress_form},
|
||||||
knowledge::show_knowledge_page,
|
knowledge::{
|
||||||
|
delete_knowledge_entity, patch_knowledge_entity,
|
||||||
|
show_edit_knowledge_entity_form, show_knowledge_page,
|
||||||
|
},
|
||||||
search_result::search_result_handler,
|
search_result::search_result_handler,
|
||||||
signin::{authenticate_user, show_signin_form},
|
signin::{authenticate_user, show_signin_form},
|
||||||
signout::sign_out_user,
|
signout::sign_out_user,
|
||||||
@@ -174,6 +177,12 @@ fn html_routes(
|
|||||||
.route("/text-content/:id", delete(delete_text_content))
|
.route("/text-content/:id", delete(delete_text_content))
|
||||||
.route("/jobs/:job_id", delete(delete_job))
|
.route("/jobs/:job_id", delete(delete_job))
|
||||||
.route("/knowledge", get(show_knowledge_page))
|
.route("/knowledge", get(show_knowledge_page))
|
||||||
|
.route(
|
||||||
|
"/knowledge-entity/:id",
|
||||||
|
get(show_edit_knowledge_entity_form)
|
||||||
|
.delete(delete_knowledge_entity)
|
||||||
|
.patch(patch_knowledge_entity),
|
||||||
|
)
|
||||||
.route("/account", get(show_account_page))
|
.route("/account", get(show_account_page))
|
||||||
.route("/admin", get(show_admin_panel))
|
.route("/admin", get(show_admin_panel))
|
||||||
.route("/toggle-registrations", patch(toggle_registration_status))
|
.route("/toggle-registrations", patch(toggle_registration_status))
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use axum::{
|
use axum::{
|
||||||
extract::{Path, State},
|
extract::{Path, State},
|
||||||
response::{IntoResponse, Redirect},
|
response::{IntoResponse, Redirect},
|
||||||
|
Form,
|
||||||
};
|
};
|
||||||
use axum_session::Session;
|
use axum_session::Session;
|
||||||
use axum_session_auth::AuthSession;
|
use axum_session_auth::AuthSession;
|
||||||
@@ -148,3 +149,87 @@ pub async fn show_knowledge_page(
|
|||||||
|
|
||||||
Ok(output.into_response())
|
Ok(output.into_response())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct EntityData {
|
||||||
|
entity: KnowledgeEntity,
|
||||||
|
user: User,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn show_edit_knowledge_entity_form(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
auth: AuthSession<User, String, SessionSurrealPool<Any>, Surreal<Any>>,
|
||||||
|
Path(id): Path<String>,
|
||||||
|
) -> Result<impl IntoResponse, HtmlError> {
|
||||||
|
// Early return if the user is not authenticated
|
||||||
|
let user = match auth.current_user {
|
||||||
|
Some(user) => user,
|
||||||
|
None => return Ok(Redirect::to("/signin").into_response()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// SORT OUT ERROR HANDLING AND VALIDATE INPUT
|
||||||
|
let entity = get_item(&state.surreal_db_client, &id)
|
||||||
|
.await
|
||||||
|
.map_err(|e| HtmlError::new(AppError::from(e), state.templates.clone()))?;
|
||||||
|
|
||||||
|
let output = render_template(
|
||||||
|
"knowledge/edit_knowledge_entity_modal.html",
|
||||||
|
EntityData {
|
||||||
|
entity: entity.unwrap(),
|
||||||
|
user,
|
||||||
|
},
|
||||||
|
state.templates,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(output.into_response())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct EntityListData {
|
||||||
|
entities: Vec<KnowledgeEntity>,
|
||||||
|
user: User,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct PatchKnowledgeEntityParams {
|
||||||
|
name: String,
|
||||||
|
description: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn patch_knowledge_entity(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
auth: AuthSession<User, String, SessionSurrealPool<Any>, Surreal<Any>>,
|
||||||
|
Form(form): Form<PatchKnowledgeEntityParams>,
|
||||||
|
) -> Result<impl IntoResponse, HtmlError> {
|
||||||
|
// Early return if the user is not authenticated
|
||||||
|
let user = match auth.current_user {
|
||||||
|
Some(user) => user,
|
||||||
|
None => return Ok(Redirect::to("/signin").into_response()),
|
||||||
|
};
|
||||||
|
|
||||||
|
info!("{:#?}", form);
|
||||||
|
|
||||||
|
let entities = User::get_knowledge_entities(&user.id, &state.surreal_db_client)
|
||||||
|
.await
|
||||||
|
.map_err(|e| HtmlError::new(e, state.templates.clone()))?;
|
||||||
|
|
||||||
|
let output = render_template(
|
||||||
|
"knowledge/entity_list.html",
|
||||||
|
EntityListData { entities, user },
|
||||||
|
state.templates,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(output.into_response())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_knowledge_entity(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
auth: AuthSession<User, String, SessionSurrealPool<Any>, Surreal<Any>>,
|
||||||
|
) -> Result<impl IntoResponse, HtmlError> {
|
||||||
|
// Early return if the user is not authenticated
|
||||||
|
let user = match auth.current_user {
|
||||||
|
Some(user) => user,
|
||||||
|
None => return Ok(Redirect::to("/signin").into_response()),
|
||||||
|
};
|
||||||
|
Ok("Thanks".into_response())
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,19 +5,19 @@
|
|||||||
|
|
||||||
<div class="stats stats-vertical lg:stats-horizontal shadow">
|
<div class="stats stats-vertical lg:stats-horizontal shadow">
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<div class="stat-title">Page loads</div>
|
<div class="stat-title font-bold">Page loads</div>
|
||||||
<div class="stat-value text-secondary">{{analytics.page_loads}}</div>
|
<div class="stat-value text-secondary">{{analytics.page_loads}}</div>
|
||||||
<div class="stat-desc">Amount of page loads</div>
|
<div class="stat-desc">Amount of page loads</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<div class="stat-title">Unique visitors</div>
|
<div class="stat-title font-bold">Unique visitors</div>
|
||||||
<div class="stat-value text-primary">{{analytics.visitors}}</div>
|
<div class="stat-value text-primary">{{analytics.visitors}}</div>
|
||||||
<div class="stat-desc">Amount of unique visitors</div>
|
<div class="stat-desc">Amount of unique visitors</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<div class="stat-title">Users</div>
|
<div class="stat-title font-bold">Users</div>
|
||||||
<div class="stat-value text-accent">{{users}}</div>
|
<div class="stat-value text-accent">{{users}}</div>
|
||||||
<div class="stat-desc">Amount of registered users</div>
|
<div class="stat-desc">Amount of registered users</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
{% block main %}{% endblock %}
|
{% block main %}{% endblock %}
|
||||||
|
|
||||||
|
<div id="modal"></div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -3,10 +3,10 @@
|
|||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, viewport-fit=cover">
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||||
<title>{% block title %}Minne{% endblock %}</title>
|
<title>{% block title %}Minne{% endblock %}</title>
|
||||||
|
|
||||||
<!-- <meta http-equiv="refresh" content="4"> -->
|
<!-- <meta http-equiv=" refresh" content="4"> -->
|
||||||
|
|
||||||
<!-- Preload critical assets -->
|
<!-- Preload critical assets -->
|
||||||
<link rel="preload" href="/assets/htmx.min.js" as="script">
|
<link rel="preload" href="/assets/htmx.min.js" as="script">
|
||||||
|
|||||||
31
templates/knowledge/edit_knowledge_entity_modal.html
Normal file
31
templates/knowledge/edit_knowledge_entity_modal.html
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{% extends "modal_base.html" %}
|
||||||
|
|
||||||
|
{% block form_attributes %}
|
||||||
|
hx-patch="/knowledge-entity/{{entity.id}}"
|
||||||
|
hx-target="#entity-list"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modal_content %}
|
||||||
|
<h3 class="text-lg font-bold mb-4">Edit Entity</h3>
|
||||||
|
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text">Entity Name</span>
|
||||||
|
</label>
|
||||||
|
<input type="text" name="name" value="{{ entity.name }}" class="input input-bordered">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-control mt-4">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text">Description</span>
|
||||||
|
</label>
|
||||||
|
<textarea name="description" class="textarea textarea-bordered h-32">{{ entity.description }}</textarea>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block primary_actions %}
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
Save Changes
|
||||||
|
</button>
|
||||||
|
{% endblock %}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<div class="grid sm:grid-cols-2 md:grid-cols-3 gap-4" id="entity_list">
|
<div class="grid sm:grid-cols-2 md:grid-cols-3 gap-4" id="entity-list">
|
||||||
{% for entity in entities %}
|
{% for entity in entities %}
|
||||||
<div class="card min-w-72 bg-base-100 shadow">
|
<div class="card min-w-72 bg-base-100 shadow">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
@@ -8,10 +8,11 @@
|
|||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<p>{{entity.updated_at | datetimeformat(format="short", tz=user.timezeone)}}</p>
|
<p>{{entity.updated_at | datetimeformat(format="short", tz=user.timezeone)}}</p>
|
||||||
<div>
|
<div>
|
||||||
<button hx-patch="/knowledge-entity/{{entity.id}}" class="btn btn-square btn-ghost btn-sm">
|
<button hx-get="/knowledge-entity/{{entity.id}}" hx-target="#modal" hx-swap="innerHTML"
|
||||||
|
class="btn btn-square btn-ghost btn-sm">
|
||||||
{% include "icons/edit_icon.html" %}
|
{% include "icons/edit_icon.html" %}
|
||||||
</button>
|
</button>
|
||||||
<button hx-delete="/knowledge-entity/{{entity.id}}" hx-target="#entity_list" hx-swap="outerHTML"
|
<button hx-delete="/knowledge-entity/{{entity.id}}" hx-target="#entity-list" hx-swap="outerHTML"
|
||||||
class="btn btn-square btn-ghost btn-sm">
|
class="btn btn-square btn-ghost btn-sm">
|
||||||
{% include "icons/delete_icon.html" %}
|
{% include "icons/delete_icon.html" %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
33
templates/modal_base.html
Normal file
33
templates/modal_base.html
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<dialog id="body_modal" class="modal">
|
||||||
|
<div class="modal-box">
|
||||||
|
<form id="modal_form" {% block form_attributes %}{% endblock %}>
|
||||||
|
{% block modal_content %}
|
||||||
|
<!-- Form fields go here in child templates -->
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
<div class="modal-action">
|
||||||
|
<!-- Close button (always visible) -->
|
||||||
|
<button type="button" class="btn" onclick="document.getElementById('body_modal').close()">
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Primary actions block -->
|
||||||
|
{% block primary_actions %}
|
||||||
|
<!-- Submit/Save buttons go here in child templates -->
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Auto-open modal when injected
|
||||||
|
document.getElementById('body_modal').showModal();
|
||||||
|
|
||||||
|
// Close modal on successful form submission
|
||||||
|
document.getElementById('modal_form').addEventListener('htmx:afterRequest', (evt) => {
|
||||||
|
if (evt.detail.successful) {
|
||||||
|
document.getElementById('body_modal').close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</dialog>
|
||||||
Reference in New Issue
Block a user