Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion my_compassion/controllers/my2_letters.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,13 +237,20 @@ def my2_render_new_letter_page(self, **kwargs):
.with_context(bin_size=False)
)

popup_templates = (
request.env["correspondence.prewritten.letter"]
.sudo()
.search([("status", "=", "active")])
)

return request.render(
"my_compassion.my2_new_letter_page",
{
"selected_child": child,
"sponsorship_ids": sponsorships,
"templates": templates,
"draft": draft,
"text_templates": popup_templates,
},
)

Expand Down Expand Up @@ -504,12 +511,20 @@ def my2_get_letter_status(self, **post):
def get_letter_templates(self, **kw):
"""
This controller returns the currently active letter template.
MODIFIED: It now accepts a 'template_id' parameter to fetch a specific one.
"""

template_id = self._safe_int(kw.get("template_id"), None)
domain = [("status", "=", "active")]
limit = 1

if template_id:
domain.append(("id", "=", template_id))

templates = (
request.env["correspondence.prewritten.letter"]
.sudo()
.search([("status", "=", "active")], limit=1)
.search(domain, limit=limit)
)

template_text = templates.text or ""
Expand All @@ -524,6 +539,7 @@ def get_letter_templates(self, **kw):
self._check_sponsored_child_access(child)
except (AccessError, ValueError):
_logger.warning("Invalid child access for letter template.")

replacements = {
"%child%": child.preferred_name or "",
"%firstname%": partner.preferred_name or partner.name or "",
Expand Down
23 changes: 2 additions & 21 deletions my_compassion/models/correspondence_prewritten_letter.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ class My2CorrespondencePreWrittenLetter(models.Model):
_rec_name = "name"

# == Fields ==
name = fields.Char(required=True)
text = fields.Text(string="Text")
name = fields.Char(required=True, translate=True)
text = fields.Text(required=True, translate=True)
start_date = fields.Date(
help="The date from which this template is valid.",
required=True,
Expand Down Expand Up @@ -122,22 +122,3 @@ def _check_non_overlapping_schedule(self):
raise ValidationError(
_("Cannot schedule a template that is already expired.")
)

# Only check for overlaps if the current record is scheduled
if record.is_active and record.start_date and record.end_date:
domain = [
("id", "!=", record.id),
("is_active", "=", True),
("start_date", "<=", record.end_date),
("end_date", ">=", record.start_date),
]

conflicting_records = self.search(domain)
if conflicting_records:
conflict_details = "\n".join(
f"{c.name} {c.start_date} to {c.end_date}"
for c in conflicting_records
)
raise ValidationError(
_("Conflicts with the following records:\n" + conflict_details)
)
1 change: 1 addition & 0 deletions my_compassion/models/correspondence_s2b_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class CorrespondenceS2BGenerator(models.Model):
user_id = fields.Many2one("res.users", index=True)
child_id = fields.Many2one("compassion.child", index=True)
partner_id = fields.Many2one(related="user_id.partner_id", readonly=True)
text_template_id = fields.Many2one("correspondence.prewritten.letter")

def set_sponsorship_from_user_and_child(self):
for generator in self:
Expand Down
92 changes: 59 additions & 33 deletions my_compassion/static/src/js/my2_letter_template_loader.js
Original file line number Diff line number Diff line change
@@ -1,43 +1,69 @@
/**
* Handles the selection of template images by adding an ID to the clicked image
* and removing it from any previously selected image.
*
* Is used in /templates/pages/my2_new_letter.xml
* Fetches a letter template from the server and populates the text input field with it.
*
* Used in /templates/pages/my2_new_letter.xml
*/
document.addEventListener("DOMContentLoaded", () => {
const textInput = document.getElementById("letter-input");
const childSelector = document.getElementById("child-dropdown");
const TEMPLATE_URL = `/my2/children/letter/templates`;
// Open the modal when the select element is clicked
document.getElementById("letter_template").addEventListener("click", function (event) {
event.preventDefault();
$("#textTemplateSelectionModal").modal("show");
});
document.body.addEventListener("click", function (event) {
const clickedButton = event.target.closest(".text-template-item");

const fetchTemplate = (childId) => {
if (!textInput) {
console.error("Required HTML element #letter-input is missing.");
return;
}
fetch(`${TEMPLATE_URL}?child_id=${childId}`)
.then((response) => {
if (!response.ok) {
throw new Error("Network response was not ok");
}
return response.json();
})
.then((data) => {
if (data && data.template_text) {
textInput.value = data.template_text;
}
})
.catch((error) => {
console.error("Failed to fetch template:", error);
});
};
if (clickedButton) {
const templateId = clickedButton.dataset.templateId;
if (!templateId) {
console.error("'data-template-id' is undefined.");
return;
}
// Store the template ID in a hidden input for later use if needed
const hiddenInput = document.getElementById("selected_template_id");
if (hiddenInput) {
hiddenInput.value = templateId;
} else {
console.error("Could not find '#selected_template_id' to store template ID.");
}

const childSelector = document.getElementById("child-dropdown");
if (!childSelector) {
console.error("Could not find '#child-dropdown'.");
return;
}
const childId = childSelector.value;

const TEMPLATE_URL = `/my2/children/letter/templates`;

if (childSelector) {
// Fetch template on initial load only if input is empty
if (!textInput.value) {
fetchTemplate(childSelector.value);
fetch(`${TEMPLATE_URL}?child_id=${childId}&template_id=${templateId}`)
.then((response) => {
// 1. Open curly brace
if (!response.ok) {
throw new Error("Network response was not ok");
}
return response.json(); // 2. Explicit return
}) // 3. Close curly brace
.then((data) => {
if (data && data.template_text) {
const targetInput = document.getElementById("letter-input");
if (targetInput) {
targetInput.value = data.template_text;
// Trigger input event so framework/listeners detect the change
targetInput.dispatchEvent(new Event("input", { bubbles: true, cancelable: true }));
} else {
console.error("Could not find '#letter-input' for insertion.");
}
} else {
console.error("Fetch did not return 'template_text'.");
}
})
.catch((error) => {
console.error("Fetch failed:", error); // Fixed typo "etch"
});
}
// Re-fetch template when child selection changes
childSelector.addEventListener("change", (event) => {
fetchTemplate(event.target.value);
});
}
});
});
5 changes: 3 additions & 2 deletions my_compassion/static/src/js/my2_new_letter_clear_button.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ document.addEventListener("DOMContentLoaded", function () {
this.$("#selected-template").remove();

// Restore the select template button label to default
const buttonLabel = this.$("#template-selection-label");
const buttonLabel = this.$("#template-selection-label, #text-template-selection-label");
if (buttonLabel.length) {
buttonLabel.text(_t("Select a template"));
}
Expand Down Expand Up @@ -81,7 +81,8 @@ document.addEventListener("DOMContentLoaded", function () {
* @private
*/
_toggleClearButton: function () {
const hasText = this.$("#letter-input").val().trim().length > 0;
const letterText = this.$("#letter-input").val();
const hasText = letterText && letterText.trim().length > 0;
const selectedTemplate = this.$("#selected-template").length > 0;
const hasAttachments = this.$("#letter-attachments")[0].files.length > 0;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ document.addEventListener("DOMContentLoaded", function () {

// Get all template images
const templateImages = document.querySelectorAll(".template-image");
const textInputs = document.querySelectorAll(".text-template-item");

// Add click event listener to each image
templateImages.forEach((image) => {
Expand Down Expand Up @@ -42,4 +43,13 @@ document.addEventListener("DOMContentLoaded", function () {
targetDiv.appendChild(img);
});
});

textInputs.forEach((textInput) => {
textInput.addEventListener("click", function () {
// Replace the button's label content with the template name selected
const button_label_element = document.getElementById("text-template-selection-label");
const selectedTemplateName = textInput.innerText;
button_label_element.textContent = selectedTemplateName;
});
});
});
69 changes: 66 additions & 3 deletions my_compassion/templates/pages/my2_new_letter.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@
</div>
<form id="new_letter_form" class="col-12 col-md-10 col-lg-8 mx-auto">
<input type="hidden" name="generator_id" t-att-value="draft.id" />
<input
type="hidden"
name="text_template_id"
id="selected_template_id"
t-att-value="draft.text_template_id.id"
/>
<div class="row align-items-center mb-3">
<div class="col-7">
<div class="form-group">
Expand Down Expand Up @@ -72,9 +78,9 @@
</t>
</div>
</div>
<!-- Button to open the modal for selecting a letter template -->
<!-- Buttons to open the modals for selecting a letter and text template -->
<div class="row align-items-center mb-3">
<div class="col-7">
<div class="col-12 col-md-4 mb-3 mb-md-0">
<div class="form-group">
<label class="text-core-blue" for="template-selection">Template</label>
<div class="d-block">
Expand All @@ -99,7 +105,27 @@
</div>
</div>
</div>
<div class="col-5 text-right">
<div class="col-12 col-md-4 mb-3 mb-md-0">
<div t-if="text_templates" class="form-group">
<label class="text-core-blue">Text suggestions</label>
<div class="d-block">
<t t-call="theme_compassion_2025.ThemedButtonComponent">
<t t-set="id" t-value="'letter_template'" />
<t t-set="variant" t-value="'default'" />
<t t-set="variation" t-value="'hollow'" />
<t t-set="label_empty">Select a template</t>
<t t-set="label_id" t-value="'text-template-selection-label'" />
<t
t-set="label"
t-value="draft.text_template_id.name if draft.text_template_id else label_empty"
/>
<t t-set="icon" t-value="'icon-pencil01'" />
<t t-set="color" t-value="'dark-blue'" />
</t>
</div>
</div>
</div>
<div class="col-12 col-md-4 text-md-right">
<div id="template-reactive-img">
<t t-if="draft and draft.template_id">
<img
Expand Down Expand Up @@ -147,6 +173,43 @@
</div>
</div>
</div>
<!-- Modal showed when the user clicks on the button for selecting a text template -->
<div
class="modal fade"
id="textTemplateSelectionModal"
tabindex="-1"
role="dialog"
aria-labelledby="textTemplateModalLabel"
aria-hidden="true"
>
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="textTemplateModalLabel">
Get inspired with a text suggestion
</h5>
<button type="button" class="close" data-dismiss="modal" t-translation="off">
x
</button>
</div>
<div class="modal-body">
<div class="list-group">
<t t-foreach="text_templates" t-as="text_tpl" t-key="text_tpl.id">
<button
type="button"
class="list-group-item list-group-item-action text-template-item"
t-att-data-template-id="text_tpl.id"
t-att-data-template-name="text_tpl.name"
Comment thread
Shayan105 marked this conversation as resolved.
data-dismiss="modal"
>
<t t-esc="text_tpl.name" />
</button>
</t>
</div>
</div>
</div>
</div>
</div>
<!-- Text area for letter writing -->
<div class="form-group">

Expand Down
Loading