diff --git a/addons/dance_attendance/__manifest__.py b/addons/dance_attendance/__manifest__.py index ee30ad2..343653a 100644 --- a/addons/dance_attendance/__manifest__.py +++ b/addons/dance_attendance/__manifest__.py @@ -14,4 +14,12 @@ ], "application": False, "installable": True, + "assets": { + "web.assets_backend": [ + "dance_attendance/static/src/sessions_page/sessions_page.js", + "dance_attendance/static/src/sessions_page/sessions_page.xml", + "dance_attendance/static/src/attendance_page/attendance_page.js", + "dance_attendance/static/src/attendance_page/attendance_page.xml", + ], + }, } diff --git a/addons/dance_attendance/static/src/attendance_page/attendance_page.js b/addons/dance_attendance/static/src/attendance_page/attendance_page.js new file mode 100644 index 0000000..e263c14 --- /dev/null +++ b/addons/dance_attendance/static/src/attendance_page/attendance_page.js @@ -0,0 +1,91 @@ +/** @odoo-module **/ + +import { Component, useState, onWillStart } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { useService } from "@web/core/utils/hooks"; + +export class DanceAttendancePage extends Component { + setup() { + this.orm = useService("orm"); + this.action = useService("action"); + + this.state = useState({ + attendanceRecords: [], + loading: false, + searchQuery: "" + }); + + onWillStart(async () => { + await this.loadAttendance(); + }); + } + + async loadAttendance() { + this.state.loading = true; + try { + let domain = []; + if (this.state.searchQuery) { + domain = [ + "|", "|", "|", + ["student_id.name", "ilike", this.state.searchQuery], + ["batch_id.name", "ilike", this.state.searchQuery], + ["session_id.name", "ilike", this.state.searchQuery], + ["remarks", "ilike", this.state.searchQuery] + ]; + } + + this.state.attendanceRecords = await this.orm.searchRead( + "dance.attendance", + domain, + ["date", "batch_id", "session_id", "student_id", "status", "remarks"], + { limit: 50, order: "date DESC" } + ); + } catch (e) { + console.error("Error loading attendance:", e); + } finally { + this.state.loading = false; + } + } + + onSearch(ev) { + if (ev.type === "keyup" && ev.key !== "Enter") { + return; + } + this.loadAttendance(); + } + + openAttendance(recordId) { + this.action.doAction({ + type: "ir.actions.act_window", + res_model: "dance.attendance", + res_id: recordId, + views: [[false, "form"]], + target: "current" + }); + } + + navigate(route) { + const routes = { + dashboard: "dance_reports.dance_school_dashboard_action", + students: { type: "ir.actions.client", name: "Students", tag: "dance_student.action_students_dashboard" }, + guardians: { type: "ir.actions.client", name: "Guardians", tag: "dance_guardian.action_guardians_dashboard" }, + batches: { type: "ir.actions.client", name: "Batches", tag: "dance_batch.action_batches_dashboard" }, + enrollments: { type: "ir.actions.client", name: "Enrollments", tag: "dance_batch.action_enrollments_dashboard" }, + events: { type: "ir.actions.client", name: "Events & Recitals", tag: "dance_event.action_events_dashboard" }, + enrollment_requests: { type: "ir.actions.client", name: "Enrollment Requests", tag: "dance_web.action_enrollment_requests_dashboard" }, + sessions: { type: "ir.actions.client", name: "Sessions", tag: "dance_attendance.action_sessions_dashboard" }, + leave_requests: { type: "ir.actions.client", name: "Leave Requests", tag: "dance_leave.action_leave_requests_dashboard" }, + attendance: { type: "ir.actions.client", name: "Attendance", tag: "dance_attendance.action_attendance_dashboard" }, + performance: { type: "ir.actions.client", name: "Performances", tag: "dance_event.action_performances_dashboard" }, + rehearsals: { type: "ir.actions.client", name: "Rehearsals", tag: "dance_event.action_rehearsals_dashboard" } + }; + + if (routes[route]) { + this.action.doAction(routes[route]); + } + } +} + +DanceAttendancePage.template = "dance_attendance.DanceAttendancePage"; + +registry.category("actions").add("dance_attendance.action_attendance_dashboard", DanceAttendancePage); diff --git a/addons/dance_attendance/static/src/attendance_page/attendance_page.xml b/addons/dance_attendance/static/src/attendance_page/attendance_page.xml new file mode 100644 index 0000000..6491806 --- /dev/null +++ b/addons/dance_attendance/static/src/attendance_page/attendance_page.xml @@ -0,0 +1,68 @@ + + + +
+ + + +
+
+
Attendance
+
+ + +
+
+ +
+
+ +

Loading Attendance...

+
+ +
+ +

No Attendance Records Found

+

Try adjusting your search.

+
+ +
+ + + + + + + + + + + + + + + + +
DateBatchSessionStudentStatusRemarks
+ + + + + + + + +
+
+
+
+
+
+
diff --git a/addons/dance_attendance/static/src/sessions_page/sessions_page.js b/addons/dance_attendance/static/src/sessions_page/sessions_page.js new file mode 100644 index 0000000..e243398 --- /dev/null +++ b/addons/dance_attendance/static/src/sessions_page/sessions_page.js @@ -0,0 +1,108 @@ +/** @odoo-module **/ + +import { Component, useState, onWillStart } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { useService } from "@web/core/utils/hooks"; + +export class DanceSessionsPage extends Component { + setup() { + this.orm = useService("orm"); + this.action = useService("action"); + + this.state = useState({ + sessions: [], + loading: false, + searchQuery: "" + }); + + onWillStart(async () => { + await this.loadSessions(); + }); + } + + async loadSessions() { + this.state.loading = true; + try { + let domain = []; + if (this.state.searchQuery) { + domain = [ + "|", "|", + ["batch_id.name", "ilike", this.state.searchQuery], + ["instructor_id.name", "ilike", this.state.searchQuery], + ["room", "ilike", this.state.searchQuery] + ]; + } + + this.state.sessions = await this.orm.searchRead( + "dance.session", + domain, + ["date", "batch_id", "start_time", "end_time", "room", "instructor_id", "attendance_count", "present_count", "absent_count", "late_count", "status"], + { limit: 50, order: "date DESC, start_time DESC" } + ); + } catch (e) { + console.error("Error loading sessions:", e); + } finally { + this.state.loading = false; + } + } + + onSearch(ev) { + if (ev.type === "keyup" && ev.key !== "Enter") { + return; + } + this.loadSessions(); + } + + openSession(sessionId) { + this.action.doAction({ + type: "ir.actions.act_window", + res_model: "dance.session", + res_id: sessionId, + views: [[false, "form"]], + target: "current" + }); + } + + createSession() { + this.action.doAction({ + type: "ir.actions.act_window", + res_model: "dance.session", + views: [[false, "form"]], + target: "current" + }); + } + + formatTime(floatTime) { + if (typeof floatTime !== "number") return "-"; + const hours = Math.floor(floatTime); + const minutes = Math.round((floatTime - hours) * 60); + const paddedHours = String(hours).padStart(2, "0"); + const paddedMinutes = String(minutes).padStart(2, "0"); + return `${paddedHours}:${paddedMinutes}`; + } + + navigate(route) { + const routes = { + dashboard: "dance_reports.dance_school_dashboard_action", + students: { type: "ir.actions.client", name: "Students", tag: "dance_student.action_students_dashboard" }, + guardians: { type: "ir.actions.client", name: "Guardians", tag: "dance_guardian.action_guardians_dashboard" }, + batches: { type: "ir.actions.client", name: "Batches", tag: "dance_batch.action_batches_dashboard" }, + enrollments: { type: "ir.actions.client", name: "Enrollments", tag: "dance_batch.action_enrollments_dashboard" }, + events: { type: "ir.actions.client", name: "Events & Recitals", tag: "dance_event.action_events_dashboard" }, + enrollment_requests: { type: "ir.actions.client", name: "Enrollment Requests", tag: "dance_web.action_enrollment_requests_dashboard" }, + sessions: { type: "ir.actions.client", name: "Sessions", tag: "dance_attendance.action_sessions_dashboard" }, + leave_requests: { type: "ir.actions.act_window", name: "Leave Requests", res_model: "dance.leave.request", views: [[false, "list"], [false, "form"]] }, + attendance: { type: "ir.actions.act_window", name: "Attendance", res_model: "dance.attendance", views: [[false, "list"], [false, "form"]] }, + performance: { type: "ir.actions.client", name: "Performances", tag: "dance_event.action_performances_dashboard" }, + rehearsals: { type: "ir.actions.client", name: "Rehearsals", tag: "dance_event.action_rehearsals_dashboard" } + }; + + if (routes[route]) { + this.action.doAction(routes[route]); + } + } +} + +DanceSessionsPage.template = "dance_attendance.DanceSessionsPage"; + +registry.category("actions").add("dance_attendance.action_sessions_dashboard", DanceSessionsPage); diff --git a/addons/dance_attendance/static/src/sessions_page/sessions_page.xml b/addons/dance_attendance/static/src/sessions_page/sessions_page.xml new file mode 100644 index 0000000..e89906b --- /dev/null +++ b/addons/dance_attendance/static/src/sessions_page/sessions_page.xml @@ -0,0 +1,82 @@ + + + +
+ + + +
+
+
Sessions
+
+ + + +
+
+ +
+
+ +

Loading Sessions...

+
+ +
+ +

No Sessions Found

+

Try adjusting your search or create a new session.

+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + +
DateBatchStart TimeEnd TimeRoomInstructorAttendance CountPresent CountAbsent CountLate CountStatus
+ + + + + + + + + + + + + +
+
+
+
+
+
+
diff --git a/addons/dance_attendance/views/dance_attendance_views.xml b/addons/dance_attendance/views/dance_attendance_views.xml index e85fcce..aba93fc 100644 --- a/addons/dance_attendance/views/dance_attendance_views.xml +++ b/addons/dance_attendance/views/dance_attendance_views.xml @@ -61,6 +61,11 @@ + + Attendance Dashboard + dance_attendance.action_attendance_dashboard + + Attendance dance.attendance @@ -68,5 +73,5 @@ - + diff --git a/addons/dance_attendance/views/dance_session_views.xml b/addons/dance_attendance/views/dance_session_views.xml index 7ef3fad..d14dccb 100644 --- a/addons/dance_attendance/views/dance_session_views.xml +++ b/addons/dance_attendance/views/dance_session_views.xml @@ -115,6 +115,11 @@ + + Sessions Dashboard + dance_attendance.action_sessions_dashboard + + Sessions dance.session @@ -122,5 +127,5 @@ - + diff --git a/addons/dance_batch/__manifest__.py b/addons/dance_batch/__manifest__.py index bcd66ec..0b95cee 100644 --- a/addons/dance_batch/__manifest__.py +++ b/addons/dance_batch/__manifest__.py @@ -12,6 +12,14 @@ "views/dance_batch_views.xml", "views/dance_enrollment_views.xml", ], + "assets": { + "web.assets_backend": [ + "dance_batch/static/src/batches_page/batches_page.js", + "dance_batch/static/src/batches_page/batches_page.xml", + "dance_batch/static/src/enrollments_page/enrollments_page.js", + "dance_batch/static/src/enrollments_page/enrollments_page.xml" + ] + }, "application": False, "installable": True, } diff --git a/addons/dance_batch/static/src/batches_page/batches_page.js b/addons/dance_batch/static/src/batches_page/batches_page.js new file mode 100644 index 0000000..dca949e --- /dev/null +++ b/addons/dance_batch/static/src/batches_page/batches_page.js @@ -0,0 +1,98 @@ +/** @odoo-module **/ + +import { Component, useState, onWillStart } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { useService } from "@web/core/utils/hooks"; + +export class DanceBatchesPage extends Component { + setup() { + this.orm = useService("orm"); + this.action = useService("action"); + + this.state = useState({ + batches: [], + loading: false, + searchQuery: "" + }); + + onWillStart(async () => { + await this.loadBatches(); + }); + } + + async loadBatches() { + this.state.loading = true; + try { + let domain = []; + if (this.state.searchQuery) { + domain = [ + "|", "|", + ["name", "ilike", this.state.searchQuery], + ["dance_style", "ilike", this.state.searchQuery], + ["instructor_id.name", "ilike", this.state.searchQuery] + ]; + } + + this.state.batches = await this.orm.searchRead( + "dance.batch", + domain, + ["name", "dance_style", "level", "instructor_id", "schedule_days", "room", "active_enrollment_count", "capacity", "status"], + { limit: 50 } + ); + } catch (e) { + console.error("Error loading batches:", e); + } finally { + this.state.loading = false; + } + } + + onSearch(ev) { + if (ev.type === "keyup" && ev.key !== "Enter") { + return; + } + this.loadBatches(); + } + + openBatch(batchId) { + this.action.doAction({ + type: "ir.actions.act_window", + res_model: "dance.batch", + res_id: batchId, + views: [[false, "form"]], + target: "current" + }); + } + + createBatch() { + this.action.doAction({ + type: "ir.actions.act_window", + res_model: "dance.batch", + views: [[false, "form"]], + target: "current" + }); + } + + navigate(route) { + const routes = { + dashboard: "dance_reports.dance_school_dashboard_action", + students: { type: "ir.actions.client", name: "Students", tag: "dance_student.action_students_dashboard" }, + guardians: { type: "ir.actions.client", name: "Guardians", tag: "dance_guardian.action_guardians_dashboard" }, + batches: { type: "ir.actions.client", name: "Batches", tag: "dance_batch.action_batches_dashboard" }, + enrollments: { type: "ir.actions.client", name: "Enrollments", tag: "dance_batch.action_enrollments_dashboard" }, + events: { type: "ir.actions.act_window", name: "Events & Recitals", res_model: "dance.event", views: [[false, "list"], [false, "form"]] }, + enrollment_requests: { type: "ir.actions.act_window", name: "Enrollment Requests", res_model: "dance.enrollment.request", views: [[false, "list"], [false, "form"]] }, + sessions: { type: "ir.actions.act_window", name: "Sessions", res_model: "dance.session", views: [[false, "list"], [false, "form"]] }, + leave_requests: { type: "ir.actions.act_window", name: "Leave Requests", res_model: "dance.leave.request", views: [[false, "list"], [false, "form"]] }, + attendance: { type: "ir.actions.act_window", name: "Attendance", res_model: "dance.attendance", views: [[false, "list"], [false, "form"]] }, + performance: { type: "ir.actions.act_window", name: "Performance", res_model: "dance.performance.record", views: [[false, "list"], [false, "form"]] } + }; + + if (routes[route]) { + this.action.doAction(routes[route]); + } + } +} + +DanceBatchesPage.template = "dance_batch.DanceBatchesPage"; + +registry.category("actions").add("dance_batch.action_batches_dashboard", DanceBatchesPage); diff --git a/addons/dance_batch/static/src/batches_page/batches_page.xml b/addons/dance_batch/static/src/batches_page/batches_page.xml new file mode 100644 index 0000000..b16b779 --- /dev/null +++ b/addons/dance_batch/static/src/batches_page/batches_page.xml @@ -0,0 +1,78 @@ + + + +
+ + + +
+
+
Batches
+
+ + + +
+
+ +
+
+ +

Loading Batches...

+
+ +
+ +

No Batches Found

+

Try adjusting your search or create a new batch.

+ +
+ +
+ + + + + + + + + + + + + + + + + + + +
NameDance StyleLevelInstructorSchedule DaysRoomCurrent StrengthCapacityStatus
+ + + + + + + + + + + +
+
+
+
+
+
+
diff --git a/addons/dance_batch/static/src/enrollments_page/enrollments_page.js b/addons/dance_batch/static/src/enrollments_page/enrollments_page.js new file mode 100644 index 0000000..5a4cbb9 --- /dev/null +++ b/addons/dance_batch/static/src/enrollments_page/enrollments_page.js @@ -0,0 +1,108 @@ +/** @odoo-module **/ + +import { Component, useState, onWillStart } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { useService } from "@web/core/utils/hooks"; + +export class DanceEnrollmentsPage extends Component { + setup() { + this.orm = useService("orm"); + this.action = useService("action"); + + this.state = useState({ + enrollments: [], + loading: false, + searchQuery: "", + hasInvoiceCount: false + }); + + onWillStart(async () => { + await this.loadEnrollments(); + }); + } + + async loadEnrollments() { + this.state.loading = true; + try { + let domain = []; + if (this.state.searchQuery) { + domain = [ + "|", "|", + ["student_id.name", "ilike", this.state.searchQuery], + ["batch_id.name", "ilike", this.state.searchQuery], + ["name", "ilike", this.state.searchQuery] + ]; + } + + // Check if invoice_count field exists on the model + const fieldsInfo = await this.orm.call("dance.enrollment", "fields_get", [], { attributes: ["string"] }); + this.state.hasInvoiceCount = !!fieldsInfo.invoice_count; + + const fieldsToFetch = ["student_id", "batch_id", "enrollment_date", "status"]; + if (this.state.hasInvoiceCount) { + fieldsToFetch.push("invoice_count"); + } + + this.state.enrollments = await this.orm.searchRead( + "dance.enrollment", + domain, + fieldsToFetch, + { limit: 50 } + ); + } catch (e) { + console.error("Error loading enrollments:", e); + } finally { + this.state.loading = false; + } + } + + onSearch(ev) { + if (ev.type === "keyup" && ev.key !== "Enter") { + return; + } + this.loadEnrollments(); + } + + openEnrollment(enrollmentId) { + this.action.doAction({ + type: "ir.actions.act_window", + res_model: "dance.enrollment", + res_id: enrollmentId, + views: [[false, "form"]], + target: "current" + }); + } + + createEnrollment() { + this.action.doAction({ + type: "ir.actions.act_window", + res_model: "dance.enrollment", + views: [[false, "form"]], + target: "current" + }); + } + + navigate(route) { + const routes = { + dashboard: "dance_reports.dance_school_dashboard_action", + students: { type: "ir.actions.client", name: "Students", tag: "dance_student.action_students_dashboard" }, + guardians: { type: "ir.actions.client", name: "Guardians", tag: "dance_guardian.action_guardians_dashboard" }, + batches: { type: "ir.actions.client", name: "Batches", tag: "dance_batch.action_batches_dashboard" }, + enrollments: { type: "ir.actions.client", name: "Enrollments", tag: "dance_batch.action_enrollments_dashboard" }, + events: { type: "ir.actions.act_window", name: "Events & Recitals", res_model: "dance.event", views: [[false, "list"], [false, "form"]] }, + enrollment_requests: { type: "ir.actions.act_window", name: "Enrollment Requests", res_model: "dance.enrollment.request", views: [[false, "list"], [false, "form"]] }, + sessions: { type: "ir.actions.act_window", name: "Sessions", res_model: "dance.session", views: [[false, "list"], [false, "form"]] }, + leave_requests: { type: "ir.actions.act_window", name: "Leave Requests", res_model: "dance.leave.request", views: [[false, "list"], [false, "form"]] }, + attendance: { type: "ir.actions.act_window", name: "Attendance", res_model: "dance.attendance", views: [[false, "list"], [false, "form"]] }, + performance: { type: "ir.actions.act_window", name: "Performance", res_model: "dance.performance.record", views: [[false, "list"], [false, "form"]] } + }; + + if (routes[route]) { + this.action.doAction(routes[route]); + } + } +} + +DanceEnrollmentsPage.template = "dance_batch.DanceEnrollmentsPage"; + +registry.category("actions").add("dance_batch.action_enrollments_dashboard", DanceEnrollmentsPage); diff --git a/addons/dance_batch/static/src/enrollments_page/enrollments_page.xml b/addons/dance_batch/static/src/enrollments_page/enrollments_page.xml new file mode 100644 index 0000000..be3924d --- /dev/null +++ b/addons/dance_batch/static/src/enrollments_page/enrollments_page.xml @@ -0,0 +1,70 @@ + + + +
+ + + +
+
+
Enrollments
+
+ + + +
+
+ +
+
+ +

Loading Enrollments...

+
+ +
+ +

No Enrollments Found

+

Try adjusting your search or create a new enrollment.

+ +
+ +
+ + + + + + + + + + + + + + + +
StudentBatchEnrollment DateStatusInvoice Count
+ + + + + + + +
+
+
+
+
+
+
diff --git a/addons/dance_batch/views/dance_batch_views.xml b/addons/dance_batch/views/dance_batch_views.xml index da98281..b52df56 100644 --- a/addons/dance_batch/views/dance_batch_views.xml +++ b/addons/dance_batch/views/dance_batch_views.xml @@ -95,6 +95,11 @@ + + Batches Dashboard + dance_batch.action_batches_dashboard + + Batches dance.batch @@ -102,5 +107,5 @@ - + diff --git a/addons/dance_batch/views/dance_enrollment_views.xml b/addons/dance_batch/views/dance_enrollment_views.xml index 03e5e7e..e4d7a13 100644 --- a/addons/dance_batch/views/dance_enrollment_views.xml +++ b/addons/dance_batch/views/dance_enrollment_views.xml @@ -56,6 +56,11 @@ + + Enrollments Dashboard + dance_batch.action_enrollments_dashboard + + Enrollments dance.enrollment @@ -63,5 +68,5 @@ - + diff --git a/addons/dance_event/__manifest__.py b/addons/dance_event/__manifest__.py index 76f57e3..1393052 100644 --- a/addons/dance_event/__manifest__.py +++ b/addons/dance_event/__manifest__.py @@ -15,4 +15,14 @@ ], "application": False, "installable": True, + "assets": { + "web.assets_backend": [ + "dance_event/static/src/events_page/events_page.js", + "dance_event/static/src/events_page/events_page.xml", + "dance_event/static/src/performances_page/performances_page.js", + "dance_event/static/src/performances_page/performances_page.xml", + "dance_event/static/src/rehearsals_page/rehearsals_page.js", + "dance_event/static/src/rehearsals_page/rehearsals_page.xml", + ], + }, } diff --git a/addons/dance_event/static/src/events_page/events_page.js b/addons/dance_event/static/src/events_page/events_page.js new file mode 100644 index 0000000..e746810 --- /dev/null +++ b/addons/dance_event/static/src/events_page/events_page.js @@ -0,0 +1,98 @@ +/** @odoo-module **/ + +import { Component, useState, onWillStart } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { useService } from "@web/core/utils/hooks"; + +export class DanceEventsPage extends Component { + setup() { + this.orm = useService("orm"); + this.action = useService("action"); + + this.state = useState({ + events: [], + loading: false, + searchQuery: "" + }); + + onWillStart(async () => { + await this.loadEvents(); + }); + } + + async loadEvents() { + this.state.loading = true; + try { + let domain = []; + if (this.state.searchQuery) { + domain = [ + "|", "|", + ["name", "ilike", this.state.searchQuery], + ["venue", "ilike", this.state.searchQuery], + ["event_type", "ilike", this.state.searchQuery] + ]; + } + + this.state.events = await this.orm.searchRead( + "dance.event", + domain, + ["name", "event_type", "date", "venue", "participant_count", "performance_count", "rehearsal_count", "state"], + { limit: 50 } + ); + } catch (e) { + console.error("Error loading events:", e); + } finally { + this.state.loading = false; + } + } + + onSearch(ev) { + if (ev.type === "keyup" && ev.key !== "Enter") { + return; + } + this.loadEvents(); + } + + openEvent(eventId) { + this.action.doAction({ + type: "ir.actions.act_window", + res_model: "dance.event", + res_id: eventId, + views: [[false, "form"]], + target: "current" + }); + } + + createEvent() { + this.action.doAction({ + type: "ir.actions.act_window", + res_model: "dance.event", + views: [[false, "form"]], + target: "current" + }); + } + + navigate(route) { + const routes = { + dashboard: "dance_reports.dance_school_dashboard_action", + students: { type: "ir.actions.client", name: "Students", tag: "dance_student.action_students_dashboard" }, + guardians: { type: "ir.actions.client", name: "Guardians", tag: "dance_guardian.action_guardians_dashboard" }, + batches: { type: "ir.actions.client", name: "Batches", tag: "dance_batch.action_batches_dashboard" }, + enrollments: { type: "ir.actions.client", name: "Enrollments", tag: "dance_batch.action_enrollments_dashboard" }, + events: { type: "ir.actions.client", name: "Events & Recitals", tag: "dance_event.action_events_dashboard" }, + enrollment_requests: { type: "ir.actions.act_window", name: "Enrollment Requests", res_model: "dance.enrollment.request", views: [[false, "list"], [false, "form"]] }, + sessions: { type: "ir.actions.act_window", name: "Sessions", res_model: "dance.session", views: [[false, "list"], [false, "form"]] }, + leave_requests: { type: "ir.actions.act_window", name: "Leave Requests", res_model: "dance.leave.request", views: [[false, "list"], [false, "form"]] }, + attendance: { type: "ir.actions.act_window", name: "Attendance", res_model: "dance.attendance", views: [[false, "list"], [false, "form"]] }, + performance: { type: "ir.actions.client", name: "Performances", tag: "dance_event.action_performances_dashboard" } + }; + + if (routes[route]) { + this.action.doAction(routes[route]); + } + } +} + +DanceEventsPage.template = "dance_event.DanceEventsPage"; + +registry.category("actions").add("dance_event.action_events_dashboard", DanceEventsPage); diff --git a/addons/dance_event/static/src/events_page/events_page.xml b/addons/dance_event/static/src/events_page/events_page.xml new file mode 100644 index 0000000..4229b6b --- /dev/null +++ b/addons/dance_event/static/src/events_page/events_page.xml @@ -0,0 +1,76 @@ + + + +
+ + + +
+
+
Events & Recitals
+
+ + + +
+
+ +
+
+ +

Loading Events...

+
+ +
+ +

No Events Found

+

Try adjusting your search or create a new event.

+ +
+ +
+ + + + + + + + + + + + + + + + + + +
Event NameTypeDateVenueParticipant CountPerformance CountRehearsal CountState
+ + + + + + + + + + +
+
+
+
+
+
+
diff --git a/addons/dance_event/static/src/performances_page/performances_page.js b/addons/dance_event/static/src/performances_page/performances_page.js new file mode 100644 index 0000000..032128c --- /dev/null +++ b/addons/dance_event/static/src/performances_page/performances_page.js @@ -0,0 +1,99 @@ +/** @odoo-module **/ + +import { Component, useState, onWillStart } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { useService } from "@web/core/utils/hooks"; + +export class DancePerformancesPage extends Component { + setup() { + this.orm = useService("orm"); + this.action = useService("action"); + + this.state = useState({ + performances: [], + loading: false, + searchQuery: "" + }); + + onWillStart(async () => { + await this.loadPerformances(); + }); + } + + async loadPerformances() { + this.state.loading = true; + try { + let domain = []; + if (this.state.searchQuery) { + domain = [ + "|", "|", + ["event_id.name", "ilike", this.state.searchQuery], + ["student_id.name", "ilike", this.state.searchQuery], + ["costume_needed", "ilike", this.state.searchQuery] + ]; + } + + this.state.performances = await this.orm.searchRead( + "dance.event.performance", + domain, + ["event_id", "student_id", "role", "costume_needed", "costume_status"], + { limit: 50 } + ); + } catch (e) { + console.error("Error loading performances:", e); + } finally { + this.state.loading = false; + } + } + + onSearch(ev) { + if (ev.type === "keyup" && ev.key !== "Enter") { + return; + } + this.loadPerformances(); + } + + openPerformance(perfId) { + this.action.doAction({ + type: "ir.actions.act_window", + res_model: "dance.event.performance", + res_id: perfId, + views: [[false, "form"]], + target: "current" + }); + } + + createPerformance() { + this.action.doAction({ + type: "ir.actions.act_window", + res_model: "dance.event.performance", + views: [[false, "form"]], + target: "current" + }); + } + + navigate(route) { + const routes = { + dashboard: "dance_reports.dance_school_dashboard_action", + students: { type: "ir.actions.client", name: "Students", tag: "dance_student.action_students_dashboard" }, + guardians: { type: "ir.actions.client", name: "Guardians", tag: "dance_guardian.action_guardians_dashboard" }, + batches: { type: "ir.actions.client", name: "Batches", tag: "dance_batch.action_batches_dashboard" }, + enrollments: { type: "ir.actions.client", name: "Enrollments", tag: "dance_batch.action_enrollments_dashboard" }, + events: { type: "ir.actions.client", name: "Events & Recitals", tag: "dance_event.action_events_dashboard" }, + enrollment_requests: { type: "ir.actions.act_window", name: "Enrollment Requests", res_model: "dance.enrollment.request", views: [[false, "list"], [false, "form"]] }, + sessions: { type: "ir.actions.act_window", name: "Sessions", res_model: "dance.session", views: [[false, "list"], [false, "form"]] }, + leave_requests: { type: "ir.actions.act_window", name: "Leave Requests", res_model: "dance.leave.request", views: [[false, "list"], [false, "form"]] }, + attendance: { type: "ir.actions.act_window", name: "Attendance", res_model: "dance.attendance", views: [[false, "list"], [false, "form"]] }, + performance: { type: "ir.actions.client", name: "Performances", tag: "dance_event.action_performances_dashboard" }, + rehearsals: { type: "ir.actions.client", name: "Rehearsals", tag: "dance_event.action_rehearsals_dashboard" } + }; + + if (routes[route]) { + this.action.doAction(routes[route]); + } + } +} + +DancePerformancesPage.template = "dance_event.DancePerformancesPage"; + +registry.category("actions").add("dance_event.action_performances_dashboard", DancePerformancesPage); diff --git a/addons/dance_event/static/src/performances_page/performances_page.xml b/addons/dance_event/static/src/performances_page/performances_page.xml new file mode 100644 index 0000000..fd5086e --- /dev/null +++ b/addons/dance_event/static/src/performances_page/performances_page.xml @@ -0,0 +1,70 @@ + + + +
+ + + +
+
+
Performances & Costumes
+
+ + + +
+
+ +
+
+ +

Loading Performances...

+
+ +
+ +

No Performances Found

+

Try adjusting your search or create a new performance.

+ +
+ +
+ + + + + + + + + + + + + + + +
EventStudentRoleCostume NeededCostume Status
+ + + + + + + +
+
+
+
+
+
+
diff --git a/addons/dance_event/static/src/rehearsals_page/rehearsals_page.js b/addons/dance_event/static/src/rehearsals_page/rehearsals_page.js new file mode 100644 index 0000000..287740b --- /dev/null +++ b/addons/dance_event/static/src/rehearsals_page/rehearsals_page.js @@ -0,0 +1,99 @@ +/** @odoo-module **/ + +import { Component, useState, onWillStart } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { useService } from "@web/core/utils/hooks"; + +export class DanceRehearsalsPage extends Component { + setup() { + this.orm = useService("orm"); + this.action = useService("action"); + + this.state = useState({ + rehearsals: [], + loading: false, + searchQuery: "" + }); + + onWillStart(async () => { + await this.loadRehearsals(); + }); + } + + async loadRehearsals() { + this.state.loading = true; + try { + let domain = []; + if (this.state.searchQuery) { + domain = [ + "|", "|", + ["name", "ilike", this.state.searchQuery], + ["event_id.name", "ilike", this.state.searchQuery], + ["room", "ilike", this.state.searchQuery] + ]; + } + + this.state.rehearsals = await this.orm.searchRead( + "dance.rehearsal", + domain, + ["event_id", "date", "display_time", "room", "attendance_count", "present_count", "absent_count", "status"], + { limit: 50 } + ); + } catch (e) { + console.error("Error loading rehearsals:", e); + } finally { + this.state.loading = false; + } + } + + onSearch(ev) { + if (ev.type === "keyup" && ev.key !== "Enter") { + return; + } + this.loadRehearsals(); + } + + openRehearsal(rehearsalId) { + this.action.doAction({ + type: "ir.actions.act_window", + res_model: "dance.rehearsal", + res_id: rehearsalId, + views: [[false, "form"]], + target: "current" + }); + } + + createRehearsal() { + this.action.doAction({ + type: "ir.actions.act_window", + res_model: "dance.rehearsal", + views: [[false, "form"]], + target: "current" + }); + } + + navigate(route) { + const routes = { + dashboard: "dance_reports.dance_school_dashboard_action", + students: { type: "ir.actions.client", name: "Students", tag: "dance_student.action_students_dashboard" }, + guardians: { type: "ir.actions.client", name: "Guardians", tag: "dance_guardian.action_guardians_dashboard" }, + batches: { type: "ir.actions.client", name: "Batches", tag: "dance_batch.action_batches_dashboard" }, + enrollments: { type: "ir.actions.client", name: "Enrollments", tag: "dance_batch.action_enrollments_dashboard" }, + events: { type: "ir.actions.client", name: "Events & Recitals", tag: "dance_event.action_events_dashboard" }, + enrollment_requests: { type: "ir.actions.act_window", name: "Enrollment Requests", res_model: "dance.enrollment.request", views: [[false, "list"], [false, "form"]] }, + sessions: { type: "ir.actions.act_window", name: "Sessions", res_model: "dance.session", views: [[false, "list"], [false, "form"]] }, + leave_requests: { type: "ir.actions.act_window", name: "Leave Requests", res_model: "dance.leave.request", views: [[false, "list"], [false, "form"]] }, + attendance: { type: "ir.actions.act_window", name: "Attendance", res_model: "dance.attendance", views: [[false, "list"], [false, "form"]] }, + performance: { type: "ir.actions.client", name: "Performances", tag: "dance_event.action_performances_dashboard" }, + rehearsals: { type: "ir.actions.client", name: "Rehearsals", tag: "dance_event.action_rehearsals_dashboard" } + }; + + if (routes[route]) { + this.action.doAction(routes[route]); + } + } +} + +DanceRehearsalsPage.template = "dance_event.DanceRehearsalsPage"; + +registry.category("actions").add("dance_event.action_rehearsals_dashboard", DanceRehearsalsPage); diff --git a/addons/dance_event/static/src/rehearsals_page/rehearsals_page.xml b/addons/dance_event/static/src/rehearsals_page/rehearsals_page.xml new file mode 100644 index 0000000..b28db1d --- /dev/null +++ b/addons/dance_event/static/src/rehearsals_page/rehearsals_page.xml @@ -0,0 +1,76 @@ + + + +
+ + + +
+
+
Rehearsals
+
+ + + +
+
+ +
+
+ +

Loading Rehearsals...

+
+ +
+ +

No Rehearsals Found

+

Try adjusting your search or schedule a new rehearsal.

+ +
+ +
+ + + + + + + + + + + + + + + + + + +
EventDateDisplay TimeRoomAttendance CountPresent CountAbsent CountStatus
+ + + + + + + + + + +
+
+
+
+
+
+
diff --git a/addons/dance_event/views/dance_event_performance_views.xml b/addons/dance_event/views/dance_event_performance_views.xml index d8c2041..a1fabb1 100644 --- a/addons/dance_event/views/dance_event_performance_views.xml +++ b/addons/dance_event/views/dance_event_performance_views.xml @@ -60,6 +60,11 @@ + + Performances Dashboard + dance_event.action_performances_dashboard + + Performances & Costumes dance.event.performance @@ -67,5 +72,5 @@ - + diff --git a/addons/dance_event/views/dance_event_views.xml b/addons/dance_event/views/dance_event_views.xml index 3a5a6c1..7869f67 100644 --- a/addons/dance_event/views/dance_event_views.xml +++ b/addons/dance_event/views/dance_event_views.xml @@ -108,6 +108,11 @@ + + Events Dashboard + dance_event.action_events_dashboard + + Events & Recitals dance.event @@ -115,6 +120,5 @@ - - + diff --git a/addons/dance_event/views/dance_rehearsal_views.xml b/addons/dance_event/views/dance_rehearsal_views.xml index 3c0d96a..93d3296 100644 --- a/addons/dance_event/views/dance_rehearsal_views.xml +++ b/addons/dance_event/views/dance_rehearsal_views.xml @@ -89,6 +89,11 @@ + + Rehearsals Dashboard + dance_event.action_rehearsals_dashboard + + Rehearsals dance.rehearsal @@ -96,5 +101,5 @@ - + diff --git a/addons/dance_guardian/__manifest__.py b/addons/dance_guardian/__manifest__.py index 275fdaf..2cdda24 100644 --- a/addons/dance_guardian/__manifest__.py +++ b/addons/dance_guardian/__manifest__.py @@ -1,7 +1,7 @@ { "name": "Dance Guardian Management", "summary": "Guardian contacts and student linking for dance school portal", - "version": "17.0.1.0.0", + "version": "17.0.1.0.3", "category": "Education", "author": "Metetroncube", "license": "LGPL-3", @@ -9,6 +9,12 @@ "data": [ "views/res_partner_guardian_views.xml", ], + "assets": { + "web.assets_backend": [ + "dance_guardian/static/src/guardians_page/guardians_page.js", + "dance_guardian/static/src/guardians_page/guardians_page.xml" + ] + }, "application": False, "installable": True, } diff --git a/addons/dance_guardian/static/src/guardians_page/guardians_page.js b/addons/dance_guardian/static/src/guardians_page/guardians_page.js new file mode 100644 index 0000000..86da138 --- /dev/null +++ b/addons/dance_guardian/static/src/guardians_page/guardians_page.js @@ -0,0 +1,102 @@ +/** @odoo-module **/ + +import { registry } from "@web/core/registry"; +import { useService } from "@web/core/utils/hooks"; +import { Component, useState, onWillStart } from "@odoo/owl"; + +export class DanceGuardiansPage extends Component { + setup() { + this.orm = useService("orm"); + this.action = useService("action"); + + this.state = useState({ + guardians: [], + loading: true, + searchQuery: "", + }); + + onWillStart(async () => { + await this.loadGuardians(); + }); + } + + async loadGuardians() { + this.state.loading = true; + let domain = [['is_guardian', '=', true], ['is_student', '=', false]]; + if (this.state.searchQuery) { + domain.push(['name', 'ilike', this.state.searchQuery]); + } + + this.state.guardians = await this.orm.searchRead( + "res.partner", + domain, + ["id", "name", "phone", "mobile", "email", "guardian_student_ids"] + ); + this.state.loading = false; + } + + onSearch(ev) { + if (ev.key === "Enter" || ev.type === "click") { + this.loadGuardians(); + } + } + + async openGuardian(id) { + let viewId = false; + try { + viewId = await this.orm.call("ir.model.data", "_xmlid_to_res_id", ["dance_guardian.view_dance_guardian_form"]); + } catch (e) { + console.warn("Could not load custom guardian form view ID", e); + } + this.action.doAction({ + type: 'ir.actions.act_window', + res_model: 'res.partner', + res_id: id, + views: [[viewId || false, 'form']], + target: 'current', + }); + } + + async createGuardian() { + let viewId = false; + try { + viewId = await this.orm.call("ir.model.data", "_xmlid_to_res_id", ["dance_guardian.view_dance_guardian_form"]); + } catch (e) { + console.warn("Could not load custom guardian form view ID", e); + } + this.action.doAction({ + type: 'ir.actions.act_window', + res_model: 'res.partner', + views: [[viewId || false, 'form']], + target: 'current', + context: { + default_is_guardian: true, + default_is_student: false + } + }); + } + + navigate(route) { + const routes = { + dashboard: "dance_reports.dance_school_dashboard_action", + students: { type: "ir.actions.client", name: "Students", tag: "dance_student.action_students_dashboard" }, + guardians: { type: "ir.actions.client", name: "Guardians", tag: "dance_guardian.action_guardians_dashboard" }, + batches: { type: "ir.actions.client", name: "Batches", tag: "dance_batch.action_batches_dashboard" }, + enrollments: { type: "ir.actions.client", name: "Enrollments", tag: "dance_batch.action_enrollments_dashboard" }, + events: { type: "ir.actions.client", name: "Events & Recitals", tag: "dance_event.action_events_dashboard" }, + enrollment_requests: { type: "ir.actions.act_window", name: "Enrollment Requests", res_model: "dance.enrollment.request", views: [[false, "list"], [false, "form"]] }, + sessions: { type: "ir.actions.act_window", name: "Sessions", res_model: "dance.session", views: [[false, "list"], [false, "form"]] }, + leave_requests: { type: "ir.actions.act_window", name: "Leave Requests", res_model: "dance.leave.request", views: [[false, "list"], [false, "form"]] }, + attendance: { type: "ir.actions.act_window", name: "Attendance", res_model: "dance.attendance", views: [[false, "list"], [false, "form"]] }, + performance: { type: "ir.actions.client", name: "Performances", tag: "dance_event.action_performances_dashboard" }, + rehearsals: { type: "ir.actions.client", name: "Rehearsals", tag: "dance_event.action_rehearsals_dashboard" } + }; + + if (routes[route]) { + this.action.doAction(routes[route]); + } + } +} + +DanceGuardiansPage.template = "dance_guardian.DanceGuardiansPage"; +registry.category("actions").add("dance_guardian.action_guardians_dashboard", DanceGuardiansPage); diff --git a/addons/dance_guardian/static/src/guardians_page/guardians_page.xml b/addons/dance_guardian/static/src/guardians_page/guardians_page.xml new file mode 100644 index 0000000..3f8174f --- /dev/null +++ b/addons/dance_guardian/static/src/guardians_page/guardians_page.xml @@ -0,0 +1,86 @@ + + + +
+ + + + + + + +
+ + +
+
Guardians Management
+
+ + + +
+
+ +
+
+ +

Loading Guardians...

+
+ +
+ +

No Guardians Found

+

Try adjusting your search or create a new guardian.

+ +
+ + +
+ + + + + + + + + + + + + + + + +
Guardian NamePhoneMobileEmailLinked Students
+
+ +
+ +
+ + + + + Student(s) + + + None + +
+
+
+
+ +
+
+
diff --git a/addons/dance_guardian/views/res_partner_guardian_views.xml b/addons/dance_guardian/views/res_partner_guardian_views.xml index 2b41cf3..b462f43 100644 --- a/addons/dance_guardian/views/res_partner_guardian_views.xml +++ b/addons/dance_guardian/views/res_partner_guardian_views.xml @@ -51,8 +51,88 @@ - + Guardians + dance_guardian.action_guardians_dashboard + + + + + dance.guardian.form + res.partner + +
+ + +
+ PARENT / GUARDIAN NAME +

+ +

+
+ + +
+
+ +
+
+ Contact Information & Address +
+ + + + + + + + + Residential Address + +
+ + + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ +
+
+ + + + Guardians List res.partner tree,form [('is_guardian', '=', True), ('is_student', '=', False)] @@ -60,7 +140,7 @@ + (0, 0, {'view_mode': 'form', 'view_id': ref('view_dance_guardian_form')})]"/> @@ -79,5 +159,6 @@ - + + diff --git a/addons/dance_leave/__manifest__.py b/addons/dance_leave/__manifest__.py index f5ee9d1..ae89a0b 100644 --- a/addons/dance_leave/__manifest__.py +++ b/addons/dance_leave/__manifest__.py @@ -13,4 +13,10 @@ ], "application": False, "installable": True, + "assets": { + "web.assets_backend": [ + "dance_leave/static/src/leave_requests_page/leave_requests_page.js", + "dance_leave/static/src/leave_requests_page/leave_requests_page.xml", + ], + }, } diff --git a/addons/dance_leave/static/src/leave_requests_page/leave_requests_page.js b/addons/dance_leave/static/src/leave_requests_page/leave_requests_page.js new file mode 100644 index 0000000..0af666a --- /dev/null +++ b/addons/dance_leave/static/src/leave_requests_page/leave_requests_page.js @@ -0,0 +1,99 @@ +/** @odoo-module **/ + +import { Component, useState, onWillStart } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { useService } from "@web/core/utils/hooks"; + +export class DanceLeaveRequestsPage extends Component { + setup() { + this.orm = useService("orm"); + this.action = useService("action"); + + this.state = useState({ + leaveRequests: [], + loading: false, + searchQuery: "" + }); + + onWillStart(async () => { + await this.loadLeaveRequests(); + }); + } + + async loadLeaveRequests() { + this.state.loading = true; + try { + let domain = []; + if (this.state.searchQuery) { + domain = [ + "|", "|", + ["student_id.name", "ilike", this.state.searchQuery], + ["guardian_id.name", "ilike", this.state.searchQuery], + ["status", "ilike", this.state.searchQuery] + ]; + } + + this.state.leaveRequests = await this.orm.searchRead( + "dance.leave.request", + domain, + ["student_id", "guardian_id", "from_date", "to_date", "status", "reviewed_by_id"], + { limit: 50, order: "from_date DESC" } + ); + } catch (e) { + console.error("Error loading leave requests:", e); + } finally { + this.state.loading = false; + } + } + + onSearch(ev) { + if (ev.type === "keyup" && ev.key !== "Enter") { + return; + } + this.loadLeaveRequests(); + } + + openLeaveRequest(requestId) { + this.action.doAction({ + type: "ir.actions.act_window", + res_model: "dance.leave.request", + res_id: requestId, + views: [[false, "form"]], + target: "current" + }); + } + + createLeaveRequest() { + this.action.doAction({ + type: "ir.actions.act_window", + res_model: "dance.leave.request", + views: [[false, "form"]], + target: "current" + }); + } + + navigate(route) { + const routes = { + dashboard: "dance_reports.dance_school_dashboard_action", + students: { type: "ir.actions.client", name: "Students", tag: "dance_student.action_students_dashboard" }, + guardians: { type: "ir.actions.client", name: "Guardians", tag: "dance_guardian.action_guardians_dashboard" }, + batches: { type: "ir.actions.client", name: "Batches", tag: "dance_batch.action_batches_dashboard" }, + enrollments: { type: "ir.actions.client", name: "Enrollments", tag: "dance_batch.action_enrollments_dashboard" }, + events: { type: "ir.actions.client", name: "Events & Recitals", tag: "dance_event.action_events_dashboard" }, + enrollment_requests: { type: "ir.actions.client", name: "Enrollment Requests", tag: "dance_web.action_enrollment_requests_dashboard" }, + sessions: { type: "ir.actions.client", name: "Sessions", tag: "dance_attendance.action_sessions_dashboard" }, + leave_requests: { type: "ir.actions.client", name: "Leave Requests", tag: "dance_leave.action_leave_requests_dashboard" }, + attendance: { type: "ir.actions.client", name: "Attendance", tag: "dance_attendance.action_attendance_dashboard" }, + performance: { type: "ir.actions.client", name: "Performances", tag: "dance_event.action_performances_dashboard" }, + rehearsals: { type: "ir.actions.client", name: "Rehearsals", tag: "dance_event.action_rehearsals_dashboard" } + }; + + if (routes[route]) { + this.action.doAction(routes[route]); + } + } +} + +DanceLeaveRequestsPage.template = "dance_leave.DanceLeaveRequestsPage"; + +registry.category("actions").add("dance_leave.action_leave_requests_dashboard", DanceLeaveRequestsPage); diff --git a/addons/dance_leave/static/src/leave_requests_page/leave_requests_page.xml b/addons/dance_leave/static/src/leave_requests_page/leave_requests_page.xml new file mode 100644 index 0000000..99127fa --- /dev/null +++ b/addons/dance_leave/static/src/leave_requests_page/leave_requests_page.xml @@ -0,0 +1,72 @@ + + + +
+ + + +
+
+
Leave Requests
+
+ + + +
+
+ +
+
+ +

Loading Leave Requests...

+
+ +
+ +

No Leave Requests Found

+

Try adjusting your search or create a new request.

+ +
+ +
+ + + + + + + + + + + + + + + + +
StudentGuardianFrom DateTo DateStatusReviewed By
+ + + + + + + + +
+
+
+
+
+
+
diff --git a/addons/dance_leave/views/dance_leave_request_views.xml b/addons/dance_leave/views/dance_leave_request_views.xml index 979c431..5db3cde 100644 --- a/addons/dance_leave/views/dance_leave_request_views.xml +++ b/addons/dance_leave/views/dance_leave_request_views.xml @@ -75,6 +75,11 @@
+ + Leave Requests Dashboard + dance_leave.action_leave_requests_dashboard + + Leave Requests dance.leave.request @@ -82,5 +87,5 @@ - + diff --git a/addons/dance_leave/views/dance_portal_leave_templates.xml b/addons/dance_leave/views/dance_portal_leave_templates.xml index 0583692..f1eae4a 100644 --- a/addons/dance_leave/views/dance_portal_leave_templates.xml +++ b/addons/dance_leave/views/dance_portal_leave_templates.xml @@ -5,33 +5,43 @@
Leave request submitted for review.
-
- Request Leave + - -
Leave Requests
- - - - From - To - Reason - Status - - - - - No leave requests found. - - - - - - - - - + +

Leave Requests

+
+
+ + + + + + + + + + + + + + + + + + + + +
FromToReasonStatus
No leave requests found.
+ + + +
+
+
@@ -42,36 +52,44 @@ Request Leave
-
- -
+ + -
- -
-
- -
-
-
- - -
-
- - -
-
- - -
+
+
+
+
+
+
+ + + + +

Request Leave for

+ +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + Cancel +
+
- - +
diff --git a/addons/dance_new_theme/__manifest__.py b/addons/dance_new_theme/__manifest__.py index ea28197..1fc2cf7 100644 --- a/addons/dance_new_theme/__manifest__.py +++ b/addons/dance_new_theme/__manifest__.py @@ -19,6 +19,12 @@ "dance_new_theme/static/src/css/contact_page.css", "dance_new_theme/static/src/js/dance_new_theme.js", ], + "web._assets_primary_variables": [ + "dance_new_theme/static/src/scss/backend_variables.scss", + ], + "web.assets_backend": [ + "dance_new_theme/static/src/scss/backend.scss", + ], }, "installable": True, "auto_install": False, diff --git a/addons/dance_new_theme/controllers/__pycache__/main.cpython-310.pyc b/addons/dance_new_theme/controllers/__pycache__/main.cpython-310.pyc index 53d6dbb..b7b78b9 100644 Binary files a/addons/dance_new_theme/controllers/__pycache__/main.cpython-310.pyc and b/addons/dance_new_theme/controllers/__pycache__/main.cpython-310.pyc differ diff --git a/addons/dance_new_theme/controllers/main.py b/addons/dance_new_theme/controllers/main.py index 640191b..96baa6b 100644 --- a/addons/dance_new_theme/controllers/main.py +++ b/addons/dance_new_theme/controllers/main.py @@ -2,11 +2,6 @@ from odoo import http # pyrefly: ignore [missing-import] from odoo.http import request # pyrefly: ignore [missing-import] -class RegistrationController(http.Controller): - @http.route('/registration', type='http', auth='public', website=True) - def registration_page(self, **post): - return request.render('dance_new_theme.registration_page_template') - class ContactController(http.Controller): @http.route('/contact', type='http', auth='public', website=True) @@ -17,4 +12,45 @@ class ContactController(http.Controller): class EventsController(http.Controller): @http.route('/events', type='http', auth='public', website=True) def events_page(self, **post): - return request.render('dance_new_theme.events_page_template') + events = request.env['dance.event'].sudo().search([('state', 'in', ['planning', 'rehearsal', 'done'])], order='date desc') + return request.render('dance_new_theme.events_page_template', {'events': events}) + + +class RegistrationController(http.Controller): + @http.route('/registration', type='http', auth='public', website=True) + def registration_page(self, **post): + return request.render('dance_new_theme.registration_page_template') + + @http.route('/registration/submit', type='http', auth='public', methods=['POST'], website=True, csrf=False) + def registration_submit(self, **post): + try: + # Create Guardian + guardian_vals = { + 'name': post.get('parent_name'), + 'phone': post.get('parent_phone'), + 'email': post.get('parent_email'), + 'mobile': post.get('parent_whatsapp'), + 'street': post.get('parent_address'), + 'city': post.get('parent_city'), + 'zip': post.get('parent_postal_code'), + 'is_student': False, + } + guardian = request.env['res.partner'].sudo().create(guardian_vals) + + # Create Student + student_vals = { + 'name': post.get('student_name'), + 'is_student': True, + 'date_of_birth': post.get('student_dob'), + 'gender': post.get('student_gender').lower() if post.get('student_gender') else False, + 'dance_style': post.get('student_dance_style').lower() if post.get('student_dance_style') else False, + 'level': post.get('student_skill_level').lower() if post.get('student_skill_level') else False, + 'medical_notes': post.get('student_medical_notes'), + 'guardian_id': guardian.id, + 'guardian_phone': guardian.phone, + } + student = request.env['res.partner'].sudo().create(student_vals) + + return "success" + except Exception as e: + return request.make_response(str(e), status=400) diff --git a/addons/dance_new_theme/static/src/scss/backend.scss b/addons/dance_new_theme/static/src/scss/backend.scss new file mode 100644 index 0000000..290cbe5 --- /dev/null +++ b/addons/dance_new_theme/static/src/scss/backend.scss @@ -0,0 +1,427 @@ +/* Admin Panel Custom Theme (Same as Website Theme) */ + +/* 1. Main Navbar Styling */ +.o_navbar, +.o_main_navbar { + background: linear-gradient(135deg, #1B132C 0%, #2C1A4D 50%, #3B1E63 100%) !important; + border-bottom: 2px solid #7434CA !important; + box-shadow: 0 2px 10px rgba(27, 19, 44, 0.4) !important; +} + +/* Ensure text and icons on the navbar are visible */ +.o_navbar .o_nav_entry, +.o_navbar .dropdown-toggle, +.o_navbar .o_menu_brand, +.o_navbar .o_menu_sections .nav-link, +.o_navbar .o_menu_systray .nav-link, +.o_navbar i.fa, +.o_main_navbar .o_nav_entry, +.o_main_navbar .dropdown-toggle, +.o_main_navbar .o_menu_brand, +.o_main_navbar .o_menu_sections .nav-link, +.o_main_navbar .o_menu_systray .nav-link, +.o_main_navbar i.fa, +.o_main_navbar .nav-link { + color: rgba(255, 255, 255, 0.9) !important; +} + +/* Hovered links on navbar */ +.o_navbar .nav-link:hover, +.o_navbar .dropdown-toggle:hover, +.o_navbar .o_menu_brand:hover, +.o_main_navbar .nav-link:hover, +.o_main_navbar .dropdown-toggle:hover, +.o_main_navbar .o_menu_brand:hover, +.o_main_navbar .o_menu_sections .o_nav_entry:hover, +.o_main_navbar .o_menu_sections a:hover, +.o_main_navbar .o_menu_sections button:hover { + background-color: rgba(255, 255, 255, 0.15) !important; + color: #ffffff !important; +} + +/* Active / Current Tab and Focused Tab on navbar - theme pill with crisp white text */ +.o_main_navbar .o_menu_sections { + .o_nav_entry, + .nav-link, + a, + button { + background-color: transparent !important; + color: rgba(255, 255, 255, 0.9) !important; + + &:hover { + background-color: rgba(255, 255, 255, 0.15) !important; + color: #ffffff !important; + } + + &:focus, + &:active, + &.focus, + &.active, + &[aria-current="page"] { + background-color: rgba(255, 255, 255, 0.22) !important; + background: rgba(255, 255, 255, 0.22) !important; + color: #ffffff !important; + font-weight: 700 !important; + border-bottom: 3px solid #ffffff !important; + border-radius: 4px !important; + } + } + + > .active, + > .nav-link.active, + > .o_nav_entry.active, + > a.active, + > button.active, + > [aria-current="page"], + > .dropdown.show > .dropdown-toggle, + > .dropdown > .dropdown-toggle.active { + background-color: rgba(255, 255, 255, 0.22) !important; + background: rgba(255, 255, 255, 0.22) !important; + color: #ffffff !important; + font-weight: 700 !important; + border-bottom: 3px solid #ffffff !important; + border-radius: 4px !important; + } +} + +/* Navbar Dropdown Menus (Performance, etc. - Visible dark text on white menu) */ +.o_main_navbar .dropdown-menu, +.o_navbar .dropdown-menu, +.o_menu_sections .dropdown-menu { + background-color: #ffffff !important; + border: 1px solid #ede4f7 !important; + box-shadow: 0 10px 30px rgba(27, 19, 44, 0.18) !important; + border-radius: 10px !important; + padding: 8px 0 !important; + + .dropdown-item, + a.dropdown-item, + span.dropdown-item, + .o_nav_entry { + color: #1B132C !important; + background-color: transparent !important; + font-weight: 600 !important; + padding: 9px 20px !important; + font-size: 0.95rem !important; + + &:hover, + &:focus { + background-color: #f3ebfa !important; + color: #7434CA !important; + } + + &.active, + &:active { + background-color: #7434CA !important; + color: #ffffff !important; + } + } + + .dropdown-header { + color: #7434CA !important; + font-weight: 700 !important; + font-size: 0.8rem !important; + text-transform: uppercase !important; + padding: 8px 20px 4px !important; + } +} + +/* 2. Logo placement in the admin panel */ +/* Add logo only to the brand menu to avoid duplicates */ +.o_main_navbar .o_menu_brand { + display: flex !important; + align-items: center !important; +} + +.o_main_navbar .o_menu_brand::before { + content: ""; + display: inline-block; + width: 80px; + height: 30px; + background-image: url('/dance_new_theme/static/src/img/new_logo.png'); + background-size: contain; + background-repeat: no-repeat; + background-position: center left; + margin-right: 15px; +} + +/* 3. App Switcher (Home Menu) Background */ +.o_home_menu_background { + background-color: #f8f1ff !important; + /* dc-lavender */ + background-image: url('/dance_new_theme/static/src/img/hero_bg.png') !important; + background-size: cover !important; + background-position: center center !important; + background-blend-mode: overlay; +} + +/* Home menu app icons text */ +.o_home_menu .o_app .o_caption { + color: #171238 !important; + text-shadow: 1px 1px 2px rgba(255, 255, 255, 0.8) !important; + font-weight: 600 !important; +} + +/* 4. Full Page Colorful Theme Backgrounds */ +.o_web_client, +.o_content, +.o_view_controller { + background-color: #f8f1ff !important; + /* Lavender background */ +} + +/* Colorful Control Panel (Top Header area above lists/forms) */ +.o_control_panel { + background: linear-gradient(135deg, #f8f1ff 0%, #e8d5fc 100%) !important; + border-bottom: 2px solid #872bd5 !important; + padding-bottom: 10px !important; +} + +.o_breadcrumb .active { + color: #872bd5 !important; + font-weight: bold; +} + +/* 5. Vibrant List Views (Tables) */ +.o_list_view { + background-color: transparent !important; +} + +.o_list_table { + background-color: #ffffff; + border-radius: 8px; + overflow: hidden; + box-shadow: 0 4px 15px rgba(135, 43, 213, 0.1); + margin-top: 10px; +} + +/* Colorful Table Headers */ +.o_list_table thead { + background: linear-gradient(to right, #872bd5, #6a1aab) !important; + color: #ffffff !important; +} + +.o_list_table thead th { + color: #ffffff !important; + border-bottom: none !important; + font-weight: 600; + text-transform: uppercase; + padding: 12px 8px !important; +} + +/* Table Rows Styling */ +.o_list_table tbody tr { + transition: all 0.2s ease; +} + +.o_list_table tbody tr:nth-child(even) { + background-color: rgba(248, 241, 255, 0.6) !important; + /* light lavender rows */ +} + +.o_list_table tbody tr:hover { + background-color: rgba(135, 43, 213, 0.15) !important; + transform: scale(1.002); +} + +/* 6. Form Views (Cards) */ +.o_dance_form_view, +.o_form_view { + background-color: transparent !important; +} + +.o_form_sheet_bg { + background-color: #f7f5fa !important; + padding: 24px 32px !important; +} + +.o_form_sheet { + background-color: #ffffff !important; + border-radius: 16px !important; + box-shadow: 0 10px 30px rgba(27, 19, 44, 0.08) !important; + border: 1px solid rgba(116, 52, 202, 0.12) !important; + padding: 32px 40px !important; +} + +/* Statusbar */ +.o_form_statusbar { + background: #ffffff !important; + border-bottom: 2px solid #ede4f7 !important; + padding: 10px 18px !important; + border-radius: 14px 14px 0 0 !important; + + .o_statusbar_status { + .o_arrow_button { + font-weight: 600 !important; + font-size: 0.85rem !important; + border-radius: 6px !important; + margin-left: 4px !important; + transition: all 0.2s ease !important; + + &.btn-primary, + &.disabled.btn-primary { + background: linear-gradient(135deg, #7434CA 0%, #531C96 100%) !important; + border-color: #7434CA !important; + color: #ffffff !important; + box-shadow: 0 3px 8px rgba(116, 52, 202, 0.25) !important; + } + + &.btn-secondary { + color: #495057 !important; + background-color: #f8f6fc !important; + border: 1px solid #ede4f7 !important; + + &:hover { + color: #7434CA !important; + background-color: #ede4f7 !important; + } + } + } + } +} + +/* Form Section Cards */ +.o_dance_section_card { + background: #faf8fd; + border: 1px solid #ede6f7; + border-radius: 12px; + padding: 20px 24px 10px; + margin-bottom: 20px; + transition: all 0.2s ease; + + &:hover { + border-color: rgba(116, 52, 202, 0.3); + box-shadow: 0 4px 14px rgba(116, 52, 202, 0.06); + } +} + +.o_dance_section_header { + font-size: 0.95rem; + font-weight: 700; + color: #1B132C; + letter-spacing: 0.4px; + text-transform: uppercase; + padding-bottom: 12px; + margin-bottom: 16px; + border-bottom: 2px solid #e9dff7; + display: flex; + align-items: center; + + i { + color: #7434CA; + font-size: 1.1rem; + } +} + +/* Inputs, selects, textareas */ +.o_form_view input.o_input, +.o_form_view textarea.o_input, +.o_form_view select.o_input, +.o_form_view .o_field_widget input, +.o_form_view .o_field_widget textarea, +.o_form_view .o_field_widget select { + border: 1px solid #D9CEE8 !important; + border-radius: 8px !important; + padding: 8px 12px !important; + background-color: #ffffff !important; + color: #171238 !important; + font-size: 0.95rem !important; + transition: all 0.2s ease !important; + + &:focus { + border-color: #7434CA !important; + box-shadow: 0 0 0 3px rgba(116, 52, 202, 0.15) !important; + background-color: #ffffff !important; + outline: none !important; + } +} + +/* Radio buttons & checkboxes */ +.o_form_view input[type="radio"], +.o_form_view input[type="checkbox"] { + accent-color: #7434CA !important; + cursor: pointer; +} + +/* Notebook Tabs in Forms */ +.o_notebook .nav-tabs { + border-bottom: 2px solid #ede4f7 !important; + gap: 8px !important; +} + +.o_notebook .nav-tabs .nav-link { + border: none !important; + border-radius: 8px 8px 0 0 !important; + padding: 10px 22px !important; + color: #555 !important; + font-weight: 600 !important; + background: transparent !important; + transition: all 0.2s ease !important; + + &:hover { + color: #7434CA !important; + background-color: #faf8fd !important; + } + + &.active { + background: linear-gradient(135deg, #7434CA 0%, #5B1FA8 100%) !important; + color: #ffffff !important; + box-shadow: 0 4px 12px rgba(116, 52, 202, 0.25) !important; + } +} + +/* Primary Buttons - Make them pop! */ +.btn-primary { + background: linear-gradient(135deg, #7434CA 0%, #531C96 100%) !important; + border: none !important; + color: #ffffff !important; + box-shadow: 0 4px 10px rgba(116, 52, 202, 0.25) !important; + transition: all 0.3s ease !important; + border-radius: 6px !important; +} + +.btn-primary:hover { + background: linear-gradient(135deg, #6a1aab, #4f0f85) !important; + box-shadow: 0 6px 15px rgba(135, 43, 213, 0.4) !important; + transform: translateY(-1px); +} + +/* Search bar styling */ +.o_searchview { + border: 2px solid #872bd5 !important; + border-radius: 20px !important; + background-color: #ffffff !important; + padding: 2px 15px !important; + box-shadow: 0 2px 8px rgba(135, 43, 213, 0.1) !important; +} + +/* ── Global Notebook Tab White Text Contrast (All Form Pages) ── */ +.o_notebook .nav-tabs .nav-link.active, +.o_notebook .nav-tabs .nav-item.show .nav-link, +.o_notebook .nav-link.active, +.nav-tabs .nav-link.active, +.o_form_view .o_notebook .nav-tabs .nav-link.active, +.o_form_view .nav-tabs .nav-link.active, +div.o_notebook ul.nav-tabs > li.nav-item > a.nav-link.active, +div.o_notebook ul.nav-tabs > li.nav-item > button.nav-link.active, +.o_notebook .nav-tabs .nav-link.active span, +.nav-tabs .nav-link.active span { + background: linear-gradient(135deg, #7434CA 0%, #5B1FA8 100%) !important; + color: #ffffff !important; + font-weight: 700 !important; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.25) !important; + border-color: #7434CA !important; + border-radius: 8px 8px 0 0 !important; + box-shadow: 0 4px 12px rgba(116, 52, 202, 0.3) !important; +} + +.o_notebook .nav-tabs .nav-link:not(.active), +.nav-tabs .nav-link:not(.active) { + color: #495057 !important; + font-weight: 600 !important; + + &:hover { + color: #7434CA !important; + background-color: #faf8fd !important; + } +} \ No newline at end of file diff --git a/addons/dance_new_theme/static/src/scss/backend_variables.scss b/addons/dance_new_theme/static/src/scss/backend_variables.scss new file mode 100644 index 0000000..e1ffc90 --- /dev/null +++ b/addons/dance_new_theme/static/src/scss/backend_variables.scss @@ -0,0 +1,11 @@ +// backend_variables.scss +$o-brand-odoo: #872bd5; +$o-brand-primary: #872bd5; +$o-brand-secondary: #171238; +$primary: #872bd5; +$secondary: #171238; +$info: #f8f1ff; + +// General Bootstrap variable overrides +$body-bg: #f8f1ff; +$body-color: #171238; \ No newline at end of file diff --git a/addons/dance_new_theme/views/events_page.xml b/addons/dance_new_theme/views/events_page.xml index 2d548aa..cd7fec6 100644 --- a/addons/dance_new_theme/views/events_page.xml +++ b/addons/dance_new_theme/views/events_page.xml @@ -388,91 +388,43 @@
- - -
-
- Competition - Winter Showcase -
-
-

Winter Showcase 2026

-
- 15 Jan 2026 • Friday + +
+
+ + Competition + + + Workshop + + + Recital + + + Annual Day + + Event Cover
-
- Chennai Auditorium +
+

+
+ +
+
+ +
+

+ +

+ View Details
-

- An exciting inter-academy competition featuring various dance styles. -

- View Details
-
- - -
-
- Workshop - Masterclass + + +
+

No upcoming events found.

-
-

Contemporary Dance Masterclass

-
- 22 Feb 2026 • Sunday -
-
- Studio Hall A -
-

- A specialized workshop to enhance your contemporary dance techniques. -

- View Details -
-
- - -
-
- Recital - Recital -
-
-

Bharatanatyam Arangetram

-
- 05 Mar 2026 • Thursday -
-
- Nritya Main Stage -
-

- A graceful solo recital presentation by our senior student. -

- View Details -
-
- - -
-
- Annual Day - Annual Day -
-
-

Annual Day Celebration

-
- 25 Apr 2026 • Saturday -
-
- Grand Convention Hall -
-

- A grand celebration showcasing the talent and dedication of our dancers. -

- View Details -
-
- +
diff --git a/addons/dance_new_theme/views/registration_page.xml b/addons/dance_new_theme/views/registration_page.xml index 3429b04..a62af6f 100644 --- a/addons/dance_new_theme/views/registration_page.xml +++ b/addons/dance_new_theme/views/registration_page.xml @@ -1046,10 +1046,10 @@
@@ -1077,10 +1077,12 @@
@@ -1089,10 +1091,10 @@
@@ -1580,7 +1582,7 @@
- +
@@ -2079,6 +2081,49 @@ showStep(nextStep); } + function submitRegistration() { + var btn = document.querySelector('.btn-next[onclick="submitRegistration()"]'); + btn.innerHTML = ' Submitting...'; + btn.disabled = true; + + var data = new FormData(); + data.append('student_name', document.getElementById('student_name').value); + data.append('student_dob', document.getElementById('student_dob').value); + data.append('student_gender', document.getElementById('student_gender').value); + data.append('student_dance_style', document.getElementById('student_dance_style').value); + data.append('student_skill_level', document.getElementById('student_skill_level').value); + data.append('student_medical_notes', document.getElementById('student_medical_notes').value); + data.append('parent_name', document.getElementById('parent_name').value); + data.append('parent_relationship', document.getElementById('parent_relationship').value); + data.append('parent_phone', document.getElementById('parent_phone').value); + data.append('parent_email', document.getElementById('parent_email').value); + data.append('parent_whatsapp', document.getElementById('parent_whatsapp').value); + data.append('parent_address', document.getElementById('parent_address').value); + data.append('parent_city', document.getElementById('parent_city').value); + data.append('parent_postal_code', document.getElementById('parent_postal_code').value); + + fetch('/registration/submit', { + method: 'POST', + body: data + }) + .then(response => { + if (response.ok) { + window.location.href = '/'; + } else { + response.text().then(text => { + alert('Registration failed: ' + text); + btn.innerHTML = 'Submit Registration '; + btn.disabled = false; + }); + } + }) + .catch(error => { + console.error('Error:', error); + alert('Registration failed. Please try again.'); + btn.innerHTML = 'Submit Registration '; + btn.disabled = false; + }); + } ]]> diff --git a/addons/dance_new_theme/views/templates.xml b/addons/dance_new_theme/views/templates.xml index a5afa87..81a8b77 100644 --- a/addons/dance_new_theme/views/templates.xml +++ b/addons/dance_new_theme/views/templates.xml @@ -3348,105 +3348,44 @@
- -
-
-
-
- SEP - 12 -
-
- -
-
-

Inter Academy Dance Competition

-
- - Madurai Convention Hall + +
+
+
+
+ +
- Registration Open -
- - - -
-
- - -
-
-
-
- OCT - 05 -
-
- -
-
-

Bharatanatyam Arangetram

-
- - Nritya Main Stage +
+ + + + + + + + + + + +
- Limited Seats -
- - - -
-
- - -
-
-
-
- DEC - 18 -
-
- -
-
-

Winter Cultural Festival

-
- - Open Air Theatre +
+

+
+ + +
+ Planning + Rehearsals + Completed
- Family Event + + +
- - -
-
- - -
-
-
-
- JAN - 10 -
-
- -
-
-

New Year Dance Gala

-
- - Chennai Auditorium -
- All Dance Styles -
- - - -
-
+
diff --git a/addons/dance_performance/__manifest__.py b/addons/dance_performance/__manifest__.py index 2171fcb..0829401 100644 --- a/addons/dance_performance/__manifest__.py +++ b/addons/dance_performance/__manifest__.py @@ -14,4 +14,12 @@ ], "application": False, "installable": True, + "assets": { + "web.assets_backend": [ + "dance_performance/static/src/grade_cards_page/grade_cards_page.js", + "dance_performance/static/src/grade_cards_page/grade_cards_page.xml", + "dance_performance/static/src/performance_criteria_page/performance_criteria_page.js", + "dance_performance/static/src/performance_criteria_page/performance_criteria_page.xml", + ], + }, } diff --git a/addons/dance_performance/static/src/grade_cards_page/grade_cards_page.js b/addons/dance_performance/static/src/grade_cards_page/grade_cards_page.js new file mode 100644 index 0000000..14b3517 --- /dev/null +++ b/addons/dance_performance/static/src/grade_cards_page/grade_cards_page.js @@ -0,0 +1,102 @@ +/** @odoo-module **/ + +import { Component, useState, onWillStart } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { useService } from "@web/core/utils/hooks"; + +export class DanceGradeCardsPage extends Component { + setup() { + this.orm = useService("orm"); + this.action = useService("action"); + + this.state = useState({ + gradeCards: [], + loading: false, + searchQuery: "" + }); + + onWillStart(async () => { + await this.loadGradeCards(); + }); + } + + async loadGradeCards() { + this.state.loading = true; + try { + let domain = []; + if (this.state.searchQuery) { + domain = [ + "|", "|", "|", + ["student_id.name", "ilike", this.state.searchQuery], + ["batch_id.name", "ilike", this.state.searchQuery], + ["instructor_id.name", "ilike", this.state.searchQuery], + ["grade", "ilike", this.state.searchQuery] + ]; + } + + this.state.gradeCards = await this.orm.searchRead( + "dance.performance.record", + domain, + ["assessment_date", "student_id", "batch_id", "instructor_id", "total_score", "total_max_score", "percentage", "grade", "state"], + { limit: 50, order: "assessment_date DESC" } + ); + } catch (e) { + console.error("Error loading grade cards:", e); + } finally { + this.state.loading = false; + } + } + + onSearch(ev) { + if (ev.type === "keyup" && ev.key !== "Enter") { + return; + } + this.loadGradeCards(); + } + + openGradeCard(recordId) { + this.action.doAction({ + type: "ir.actions.act_window", + res_model: "dance.performance.record", + res_id: recordId, + views: [[false, "form"]], + target: "current" + }); + } + + createGradeCard() { + this.action.doAction({ + type: "ir.actions.act_window", + res_model: "dance.performance.record", + views: [[false, "form"]], + target: "current" + }); + } + + navigate(route) { + const routes = { + dashboard: "dance_reports.dance_school_dashboard_action", + students: { type: "ir.actions.client", name: "Students", tag: "dance_student.action_students_dashboard" }, + guardians: { type: "ir.actions.client", name: "Guardians", tag: "dance_guardian.action_guardians_dashboard" }, + batches: { type: "ir.actions.client", name: "Batches", tag: "dance_batch.action_batches_dashboard" }, + enrollments: { type: "ir.actions.client", name: "Enrollments", tag: "dance_batch.action_enrollments_dashboard" }, + events: { type: "ir.actions.client", name: "Events & Recitals", tag: "dance_event.action_events_dashboard" }, + enrollment_requests: { type: "ir.actions.client", name: "Enrollment Requests", tag: "dance_web.action_enrollment_requests_dashboard" }, + sessions: { type: "ir.actions.client", name: "Sessions", tag: "dance_attendance.action_sessions_dashboard" }, + leave_requests: { type: "ir.actions.client", name: "Leave Requests", tag: "dance_leave.action_leave_requests_dashboard" }, + attendance: { type: "ir.actions.client", name: "Attendance", tag: "dance_attendance.action_attendance_dashboard" }, + performance: { type: "ir.actions.client", name: "Performances", tag: "dance_event.action_performances_dashboard" }, + rehearsals: { type: "ir.actions.client", name: "Rehearsals", tag: "dance_event.action_rehearsals_dashboard" }, + grade_cards: { type: "ir.actions.client", name: "Grade Cards", tag: "dance_performance.action_grade_cards_dashboard" }, + performance_criteria: { type: "ir.actions.client", name: "Performance Criteria", tag: "dance_performance.action_performance_criteria_dashboard" } + }; + + if (routes[route]) { + this.action.doAction(routes[route]); + } + } +} + +DanceGradeCardsPage.template = "dance_performance.DanceGradeCardsPage"; + +registry.category("actions").add("dance_performance.action_grade_cards_dashboard", DanceGradeCardsPage); diff --git a/addons/dance_performance/static/src/grade_cards_page/grade_cards_page.xml b/addons/dance_performance/static/src/grade_cards_page/grade_cards_page.xml new file mode 100644 index 0000000..062ee0d --- /dev/null +++ b/addons/dance_performance/static/src/grade_cards_page/grade_cards_page.xml @@ -0,0 +1,78 @@ + + + +
+ + + +
+
+
Grade Cards
+
+ + + +
+
+ +
+
+ +

Loading Grade Cards...

+
+ +
+ +

No Grade Cards Found

+

Try adjusting your search or create a new grade card.

+ +
+ +
+ + + + + + + + + + + + + + + + + + + +
Assessment DateStudentBatchInstructorTotal ScoreTotal Max ScorePercentageGradeState
+ + + + + + + + + + + +
+
+
+
+
+
+
diff --git a/addons/dance_performance/static/src/performance_criteria_page/performance_criteria_page.js b/addons/dance_performance/static/src/performance_criteria_page/performance_criteria_page.js new file mode 100644 index 0000000..e523234 --- /dev/null +++ b/addons/dance_performance/static/src/performance_criteria_page/performance_criteria_page.js @@ -0,0 +1,96 @@ +/** @odoo-module **/ + +import { Component, useState, onWillStart } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { useService } from "@web/core/utils/hooks"; + +export class DancePerformanceCriteriaPage extends Component { + setup() { + this.orm = useService("orm"); + this.action = useService("action"); + + this.state = useState({ + criteria: [], + loading: false, + searchQuery: "" + }); + + onWillStart(async () => { + await this.loadCriteria(); + }); + } + + async loadCriteria() { + this.state.loading = true; + try { + let domain = []; + if (this.state.searchQuery) { + domain = [["name", "ilike", this.state.searchQuery]]; + } + + this.state.criteria = await this.orm.searchRead( + "dance.performance.criterion", + domain, + ["sequence", "name", "max_score", "active"], + { limit: 50, order: "sequence ASC" } + ); + } catch (e) { + console.error("Error loading criteria:", e); + } finally { + this.state.loading = false; + } + } + + onSearch(ev) { + if (ev.type === "keyup" && ev.key !== "Enter") { + return; + } + this.loadCriteria(); + } + + openCriterion(recordId) { + this.action.doAction({ + type: "ir.actions.act_window", + res_model: "dance.performance.criterion", + res_id: recordId, + views: [[false, "form"]], + target: "current" + }); + } + + createCriterion() { + this.action.doAction({ + type: "ir.actions.act_window", + res_model: "dance.performance.criterion", + views: [[false, "form"]], + target: "current" + }); + } + + navigate(route) { + const routes = { + dashboard: "dance_reports.dance_school_dashboard_action", + students: { type: "ir.actions.client", name: "Students", tag: "dance_student.action_students_dashboard" }, + guardians: { type: "ir.actions.client", name: "Guardians", tag: "dance_guardian.action_guardians_dashboard" }, + batches: { type: "ir.actions.client", name: "Batches", tag: "dance_batch.action_batches_dashboard" }, + enrollments: { type: "ir.actions.client", name: "Enrollments", tag: "dance_batch.action_enrollments_dashboard" }, + events: { type: "ir.actions.client", name: "Events & Recitals", tag: "dance_event.action_events_dashboard" }, + enrollment_requests: { type: "ir.actions.client", name: "Enrollment Requests", tag: "dance_web.action_enrollment_requests_dashboard" }, + sessions: { type: "ir.actions.client", name: "Sessions", tag: "dance_attendance.action_sessions_dashboard" }, + leave_requests: { type: "ir.actions.client", name: "Leave Requests", tag: "dance_leave.action_leave_requests_dashboard" }, + attendance: { type: "ir.actions.client", name: "Attendance", tag: "dance_attendance.action_attendance_dashboard" }, + performance: { type: "ir.actions.client", name: "Performances", tag: "dance_event.action_performances_dashboard" }, + rehearsals: { type: "ir.actions.client", name: "Rehearsals", tag: "dance_event.action_rehearsals_dashboard" }, + grade_cards: { type: "ir.actions.client", name: "Grade Cards", tag: "dance_performance.action_grade_cards_dashboard" }, + performance_criteria: { type: "ir.actions.client", name: "Performance Criteria", tag: "dance_performance.action_performance_criteria_dashboard" } + }; + + if (routes[route]) { + this.action.doAction(routes[route]); + } + } +} + +DancePerformanceCriteriaPage.template = "dance_performance.DancePerformanceCriteriaPage"; + +registry.category("actions").add("dance_performance.action_performance_criteria_dashboard", DancePerformanceCriteriaPage); diff --git a/addons/dance_performance/static/src/performance_criteria_page/performance_criteria_page.xml b/addons/dance_performance/static/src/performance_criteria_page/performance_criteria_page.xml new file mode 100644 index 0000000..802b28a --- /dev/null +++ b/addons/dance_performance/static/src/performance_criteria_page/performance_criteria_page.xml @@ -0,0 +1,67 @@ + + + +
+ + + +
+
+
Performance Criteria
+
+ + + +
+
+ +
+
+ +

Loading Criteria...

+
+ +
+ +

No Performance Criteria Found

+

Try adjusting your search or create a new criterion.

+ +
+ +
+ + + + + + + + + + + + + + +
NameMax ScoreActive
+ + + + + +
+
+
+
+
+
+
diff --git a/addons/dance_performance/views/dance_performance_views.xml b/addons/dance_performance/views/dance_performance_views.xml index 6fff2e4..3cdbcc7 100644 --- a/addons/dance_performance/views/dance_performance_views.xml +++ b/addons/dance_performance/views/dance_performance_views.xml @@ -134,6 +134,16 @@ + + Grade Cards Dashboard + dance_performance.action_grade_cards_dashboard + + + + Performance Criteria Dashboard + dance_performance.action_performance_criteria_dashboard + + Grade Cards dance.performance.record @@ -149,6 +159,6 @@ - - + + diff --git a/addons/dance_performance/views/dance_portal_templates.xml b/addons/dance_performance/views/dance_portal_templates.xml index 8bb3105..27c8b5c 100644 --- a/addons/dance_performance/views/dance_portal_templates.xml +++ b/addons/dance_performance/views/dance_portal_templates.xml @@ -1,35 +1,43 @@ diff --git a/addons/dance_portal/controllers/__pycache__/portal.cpython-310.pyc b/addons/dance_portal/controllers/__pycache__/portal.cpython-310.pyc index d441eb8..35737e9 100644 Binary files a/addons/dance_portal/controllers/__pycache__/portal.cpython-310.pyc and b/addons/dance_portal/controllers/__pycache__/portal.cpython-310.pyc differ diff --git a/addons/dance_portal/controllers/portal.py b/addons/dance_portal/controllers/portal.py index 2b90fe9..2b2d4bd 100644 --- a/addons/dance_portal/controllers/portal.py +++ b/addons/dance_portal/controllers/portal.py @@ -19,7 +19,7 @@ class DanceCustomerPortal(CustomerPortal): ("is_student", "=", True), "|", ("guardian_id", "=", partner.id), - ("guardian_ids", "in", partner.id), + ("guardian_ids", "in", [partner.id]), ] def _get_portal_student(self, student_id): diff --git a/addons/dance_portal/views/dance_portal_templates.xml b/addons/dance_portal/views/dance_portal_templates.xml index 6b939c5..bae801b 100644 --- a/addons/dance_portal/views/dance_portal_templates.xml +++ b/addons/dance_portal/views/dance_portal_templates.xml @@ -1,5 +1,250 @@ + + + + + + diff --git a/addons/dance_reports/static/src/dance_global.scss b/addons/dance_reports/static/src/dance_global.scss index f8405aa..9e4cd2d 100644 --- a/addons/dance_reports/static/src/dance_global.scss +++ b/addons/dance_reports/static/src/dance_global.scss @@ -3,10 +3,11 @@ // Brand: #875A7B (mauve-purple) | accent: #5e3d57 // ── Top nav bar ──────────────────────────────────────────────────────────── +.o_navbar, .o_main_navbar { - background: linear-gradient(135deg, #875A7B 0%, #5e3d57 100%) !important; - border-bottom: none !important; - box-shadow: 0 2px 12px rgba(135, 90, 123, 0.3) !important; + background: linear-gradient(135deg, #1B132C 0%, #2C1A4D 50%, #3B1E63 100%) !important; + border-bottom: 2px solid #7434CA !important; + box-shadow: 0 2px 12px rgba(27, 19, 44, 0.4) !important; .o_menu_brand { color: #fff !important; @@ -14,9 +15,12 @@ letter-spacing: -0.2px !important; } + /* Top navbar main links */ .o_nav_entry, - .o_menu_sections > .o_nav_entry > a, - .o_menu_sections .o_dropdown_menu .o_nav_entry > a { + .o_menu_sections > .nav-link, + .o_menu_sections > .dropdown > .dropdown-toggle, + .o_menu_sections > a, + .o_menu_sections > button { color: rgba(255, 255, 255, 0.9) !important; &:hover { @@ -25,12 +29,54 @@ } } - // Active breadcrumb / current app highlight - .o_menu_sections .o_nav_entry.active > a, - .o_menu_sections .o_nav_entry > a.active { - color: #fff !important; - background: rgba(255, 255, 255, 0.2) !important; - border-radius: 6px; + /* Top navbar dropdown menus (Performance dropdown, etc.) */ + .dropdown-menu { + background-color: #ffffff !important; + border: 1px solid #ede4f7 !important; + box-shadow: 0 10px 30px rgba(27, 19, 44, 0.18) !important; + border-radius: 10px !important; + + .dropdown-item, + a.dropdown-item, + span.dropdown-item { + color: #1B132C !important; + background-color: transparent !important; + font-weight: 500 !important; + padding: 8px 18px !important; + + &:hover, + &:focus { + background-color: #f3ebfa !important; + color: #7434CA !important; + } + + &.active, + &:active { + background-color: #7434CA !important; + color: #ffffff !important; + } + } + } + + // Active tab / current page highlight - theme pill with crisp white text + .o_menu_sections > .active, + .o_menu_sections > .nav-link.active, + .o_menu_sections > .o_nav_entry.active, + .o_menu_sections > a.active, + .o_menu_sections > button.active, + .o_menu_sections > [aria-current="page"], + .o_menu_sections > .dropdown.show > .dropdown-toggle, + .o_menu_sections > .dropdown > .dropdown-toggle.active, + .o_menu_sections .o_nav_entry:focus, + .o_menu_sections .o_nav_entry:active, + .o_menu_sections a:focus, + .o_menu_sections a:active { + background-color: rgba(255, 255, 255, 0.22) !important; + background: rgba(255, 255, 255, 0.22) !important; + color: #ffffff !important; + font-weight: 700 !important; + border-bottom: 3px solid #ffffff !important; + border-radius: 4px !important; } // Icons and caret @@ -175,10 +221,24 @@ } // Notebook tabs - .o_notebook .nav-tabs .nav-link.active { - color: #875A7B !important; - border-bottom-color: #875A7B !important; - font-weight: 600; + .o_notebook .nav-tabs .nav-link.active, + .nav-tabs .nav-link.active { + background: linear-gradient(135deg, #7434CA 0%, #5B1FA8 100%) !important; + color: #ffffff !important; + border-color: #7434CA !important; + font-weight: 700 !important; + border-radius: 8px 8px 0 0 !important; + } + + .o_notebook .nav-tabs .nav-link:not(.active), + .nav-tabs .nav-link:not(.active) { + color: #495057 !important; + font-weight: 600 !important; + + &:hover { + color: #7434CA !important; + background-color: #f8f6fc !important; + } } } diff --git a/addons/dance_student/__manifest__.py b/addons/dance_student/__manifest__.py index 07405fb..9db1279 100644 --- a/addons/dance_student/__manifest__.py +++ b/addons/dance_student/__manifest__.py @@ -1,7 +1,7 @@ { "name": "Dance Student Management", "summary": "Student profile fields on contacts for dance school ERP", - "version": "17.0.1.0.0", + "version": "17.0.1.0.5", "category": "Education", "author": "Metetroncube", "license": "LGPL-3", @@ -10,6 +10,13 @@ "data/student_sequence.xml", "views/res_partner_student_views.xml", ], + "assets": { + "web.assets_backend": [ + "dance_student/static/src/students_page/students_page.js", + "dance_student/static/src/students_page/students_page.xml", + "dance_student/static/src/students_page/students_page.scss", + ], + }, "application": True, "installable": True, } diff --git a/addons/dance_student/static/src/students_page/students_page.js b/addons/dance_student/static/src/students_page/students_page.js new file mode 100644 index 0000000..8e55d12 --- /dev/null +++ b/addons/dance_student/static/src/students_page/students_page.js @@ -0,0 +1,104 @@ +/** @odoo-module **/ + +import { registry } from "@web/core/registry"; +import { useService } from "@web/core/utils/hooks"; +import { Component, useState, onWillStart } from "@odoo/owl"; + +export class DanceStudentsPage extends Component { + setup() { + this.orm = useService("orm"); + this.action = useService("action"); + + this.state = useState({ + students: [], + loading: true, + searchQuery: "", + }); + + onWillStart(async () => { + await this.loadStudents(); + }); + } + + async loadStudents() { + this.state.loading = true; + let domain = [['is_student', '=', true]]; + if (this.state.searchQuery) { + domain.push(['name', 'ilike', this.state.searchQuery]); + } + + this.state.students = await this.orm.searchRead( + "res.partner", + domain, + ["id", "name", "student_code", "phone", "email", "dance_style", "level", "student_status", "join_date", "image_1920"] + ); + this.state.loading = false; + } + + onSearch(ev) { + if (ev.key === "Enter" || ev.type === "click") { + this.loadStudents(); + } + } + + async openStudent(id) { + let viewId = false; + try { + viewId = await this.orm.call("ir.model.data", "_xmlid_to_res_id", ["dance_student.view_dance_student_form"]); + } catch (e) { + console.warn("Could not load custom student form view ID", e); + } + this.action.doAction({ + type: 'ir.actions.act_window', + res_model: 'res.partner', + res_id: id, + views: [[viewId || false, 'form']], + target: 'current', + }); + } + + async createStudent() { + let viewId = false; + try { + viewId = await this.orm.call("ir.model.data", "_xmlid_to_res_id", ["dance_student.view_dance_student_form"]); + } catch (e) { + console.warn("Could not load custom student form view ID", e); + } + this.action.doAction({ + type: 'ir.actions.act_window', + res_model: 'res.partner', + views: [[viewId || false, 'form']], + target: 'current', + context: { + default_is_student: true, + default_student_status: 'trial' + } + }); + } + + navigate(route) { + const routes = { + dashboard: "dance_reports.dance_school_dashboard_action", + students: { type: "ir.actions.client", name: "Students", tag: "dance_student.action_students_dashboard" }, + guardians: { type: "ir.actions.client", name: "Guardians", tag: "dance_guardian.action_guardians_dashboard" }, + batches: { type: "ir.actions.client", name: "Batches", tag: "dance_batch.action_batches_dashboard" }, + enrollments: { type: "ir.actions.client", name: "Enrollments", tag: "dance_batch.action_enrollments_dashboard" }, + events: { type: "ir.actions.client", name: "Events & Recitals", tag: "dance_event.action_events_dashboard" }, + enrollment_requests: { type: "ir.actions.client", name: "Enrollment Requests", tag: "dance_web.action_enrollment_requests_dashboard" }, + sessions: { type: "ir.actions.client", name: "Sessions", tag: "dance_attendance.action_sessions_dashboard" }, + leave_requests: { type: "ir.actions.client", name: "Leave Requests", tag: "dance_leave.action_leave_requests_dashboard" }, + attendance: { type: "ir.actions.client", name: "Attendance", tag: "dance_attendance.action_attendance_dashboard" }, + performance: { type: "ir.actions.client", name: "Performances", tag: "dance_event.action_performances_dashboard" }, + rehearsals: { type: "ir.actions.client", name: "Rehearsals", tag: "dance_event.action_rehearsals_dashboard" }, + grade_cards: { type: "ir.actions.client", name: "Grade Cards", tag: "dance_performance.action_grade_cards_dashboard" }, + performance_criteria: { type: "ir.actions.client", name: "Performance Criteria", tag: "dance_performance.action_performance_criteria_dashboard" } + }; + + if (routes[route]) { + this.action.doAction(routes[route]); + } + } +} + +DanceStudentsPage.template = "dance_student.DanceStudentsPage"; +registry.category("actions").add("dance_student.action_students_dashboard", DanceStudentsPage); diff --git a/addons/dance_student/static/src/students_page/students_page.scss b/addons/dance_student/static/src/students_page/students_page.scss new file mode 100644 index 0000000..ef266e5 --- /dev/null +++ b/addons/dance_student/static/src/students_page/students_page.scss @@ -0,0 +1,686 @@ +/* Custom Student Page Styling */ +.stu-wrap { + height: 100vh; + display: flex; + flex-direction: row; + background-color: #F8F9FA; + overflow: hidden; +} + +/* Sidebar */ +.stu-sidebar { + width: 250px; + background: linear-gradient(180deg, #1B132C 0%, #301f4c 100%); + color: white; + display: flex; + flex-direction: column; + box-shadow: 4px 0 15px rgba(0,0,0,0.1); + z-index: 10; +} + +.stu-sidebar-brand { + padding: 25px 20px; + font-size: 1.5rem; + font-weight: 700; + display: flex; + align-items: center; + gap: 12px; + border-bottom: 1px solid rgba(255,255,255,0.1); +} + +.stu-sidebar-nav { + padding: 20px 0; + flex: 1; + overflow-y: auto; +} + +/* Hide scrollbar for Chrome, Safari and Opera */ +.stu-sidebar-nav::-webkit-scrollbar { + display: none; +} +/* Hide scrollbar for IE, Edge and Firefox */ +.stu-sidebar-nav { + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ +} + +.stu-nav-item { + padding: 15px 25px; + font-size: 1.1rem; + cursor: pointer; + display: flex; + align-items: center; + gap: 12px; + color: rgba(255,255,255,0.7); + transition: all 0.2s; +} + +.stu-nav-item:hover, .stu-nav-item.active { + background-color: rgba(255,255,255,0.1); + color: white; + border-left: 4px solid #7434CA; +} + +/* Sidebar Submenu */ +.stu-nav-group { + display: flex; + flex-direction: column; + margin-bottom: 5px; +} +.stu-nav-group-title { + padding: 15px 25px 5px; + font-size: 0.75rem; + font-weight: 700; + color: rgba(255,255,255,0.4); + letter-spacing: 1px; + text-transform: uppercase; +} +.stu-nav-subitem { + padding: 10px 25px 10px 45px; + font-size: 1rem; + cursor: pointer; + display: flex; + align-items: center; + gap: 12px; + color: rgba(255,255,255,0.7); + transition: all 0.2s; + border-left: 4px solid transparent; +} +.stu-nav-subitem:hover, .stu-nav-subitem.active { + background-color: rgba(255,255,255,0.05); + color: white; + border-left: 4px solid #7434CA; +} + +/* Main Content Wrapper */ +.stu-main { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; +} + +/* Top Bar */ +.stu-top-bar { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px 30px; + background-color: white; + box-shadow: 0 2px 10px rgba(0,0,0,0.05); + z-index: 5; +} + +.stu-top-title { + font-size: 1.4rem; + font-weight: 700; + color: #171238; +} + +.stu-header-actions { + display: flex; + align-items: center; + gap: 15px; +} + +/* Search Box */ +.stu-search { + position: relative; + display: flex; + align-items: center; +} +.stu-search input { + padding: 10px 15px 10px 35px; + border-radius: 20px; + border: 1px solid #EAECEF; + outline: none; + width: 250px; + font-size: 0.95rem; + background-color: #F8F9FA; + color: #333; + transition: all 0.2s ease; +} +.stu-search input::placeholder { + color: #ADB5BD; +} +.stu-search input:focus { + background-color: white; + border-color: #7434CA; + box-shadow: 0 0 0 3px rgba(116,52,202,0.1); +} +.stu-search i { + position: absolute; + left: 12px; + color: #ADB5BD; +} + +/* Buttons */ +.stu-btn { + border: none; + border-radius: 6px; + padding: 10px 20px; + font-weight: 600; + cursor: pointer; + display: flex; + align-items: center; + gap: 8px; + transition: all 0.2s ease; +} +.stu-btn-primary { + background-color: #7434CA; + color: white; +} +.stu-btn-primary:hover { + background-color: #5d29a3; + transform: translateY(-2px); + box-shadow: 0 4px 10px rgba(116, 52, 202, 0.3); +} +.stu-btn-secondary { + background-color: #EAECEF; + color: #495057; + padding: 10px 15px; +} +.stu-btn-secondary:hover { + background-color: #DEE2E6; +} + +/* Content Area */ +.stu-content { + flex: 1; + overflow-y: auto; + padding: 30px; +} + +/* Loading & Empty States */ +.stu-loading, .stu-empty { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; + color: #6C757D; + gap: 15px; +} + +/* Table Card Layout */ +.stu-table-card { + background-color: white; + border-radius: 12px; + box-shadow: 0 4px 15px rgba(0,0,0,0.05); + border: 1px solid #EAECEF; + overflow: hidden; +} + +.stu-table { + width: 100%; + border-collapse: collapse; + text-align: left; +} + +.stu-table thead { + background-color: #F8F9FA; + border-bottom: 2px solid #EAECEF; +} + +.stu-table th { + padding: 15px 20px; + color: #6C757D; + font-weight: 600; + font-size: 0.85rem; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.stu-table tbody tr { + border-bottom: 1px solid #F1F3F5; + cursor: pointer; + transition: all 0.2s; +} + +.stu-table tbody tr:hover { + background-color: #F8F9FA; +} + +.stu-table tbody tr:last-child { + border-bottom: none; +} + +.stu-table td { + padding: 15px 20px; + vertical-align: middle; + color: #495057; + font-size: 0.95rem; +} + +.stu-td-id { + font-weight: 600; + color: #171238; +} + +.stu-td-name { + display: flex; + align-items: center; + gap: 12px; + font-weight: 600; + color: #171238; +} + +.stu-table-avatar { + width: 40px; + height: 40px; + border-radius: 50%; + overflow: hidden; + background-color: #EAECEF; + display: flex; + align-items: center; + justify-content: center; + color: #ADB5BD; +} +.stu-table-avatar img { + width: 100%; + height: 100%; + object-fit: cover; +} + +/* Badges */ +.stu-badge { + padding: 5px 12px; + border-radius: 20px; + font-size: 0.75rem; + font-weight: 700; + display: inline-block; +} +.stu-badge-active { background-color: #D1E7DD; color: #0F5132; } +.stu-badge-trial { background-color: #FFF3CD; color: #854000; } +.stu-badge-hold { background-color: #E2E3E5; color: #41464B; } + +/* ── Top Navbar Theme & Active Tab Contrast Fix ── */ +.o_navbar, +.o_main_navbar { + background: linear-gradient(135deg, #1B132C 0%, #2C1A4D 50%, #3B1E63 100%) !important; + border-bottom: 2px solid #7434CA !important; + box-shadow: 0 2px 10px rgba(27, 19, 44, 0.4) !important; +} + +.o_main_navbar .o_menu_brand { + color: #ffffff !important; + font-weight: 700 !important; +} + +.o_main_navbar .o_menu_sections > .o_nav_entry, +.o_main_navbar .o_menu_sections > .nav-link, +.o_main_navbar .o_menu_sections > .dropdown > .dropdown-toggle, +.o_main_navbar .o_menu_sections > a, +.o_main_navbar .o_menu_sections > button { + color: rgba(255, 255, 255, 0.88) !important; + font-weight: 500 !important; + + &:hover { + background-color: rgba(255, 255, 255, 0.15) !important; + color: #ffffff !important; + } +} + +/* Active / Current Tab on navbar: Theme pill with crisp white text */ +.o_main_navbar .o_menu_sections > .active, +.o_main_navbar .o_menu_sections > .nav-link.active, +.o_main_navbar .o_menu_sections > .o_nav_entry.active, +.o_main_navbar .o_menu_sections > a.active, +.o_main_navbar .o_menu_sections > button.active, +.o_main_navbar .o_menu_sections > [aria-current="page"], +.o_main_navbar .o_menu_sections > .dropdown.show > .dropdown-toggle, +.o_main_navbar .o_menu_sections > .dropdown > .dropdown-toggle.active, +.o_main_navbar .o_menu_sections .o_nav_entry:focus, +.o_main_navbar .o_menu_sections .o_nav_entry:active, +.o_main_navbar .o_menu_sections a:focus, +.o_main_navbar .o_menu_sections a:active { + background-color: rgba(255, 255, 255, 0.22) !important; + background: rgba(255, 255, 255, 0.22) !important; + color: #ffffff !important; + font-weight: 700 !important; + border-bottom: 3px solid #ffffff !important; + border-radius: 4px !important; +} + +/* Top Navbar Dropdown Menus (Performance, etc. - Visible dark text on white menu) */ +.o_main_navbar .dropdown-menu, +.o_navbar .dropdown-menu, +.o_menu_sections .dropdown-menu { + background-color: #ffffff !important; + border: 1px solid #ede4f7 !important; + box-shadow: 0 10px 30px rgba(27, 19, 44, 0.18) !important; + border-radius: 10px !important; + padding: 8px 0 !important; + + .dropdown-item, + a.dropdown-item, + span.dropdown-item, + .o_nav_entry { + color: #1B132C !important; + background-color: transparent !important; + font-weight: 600 !important; + padding: 9px 20px !important; + font-size: 0.95rem !important; + + &:hover, + &:focus { + background-color: #f3ebfa !important; + color: #7434CA !important; + } + + &.active, + &:active { + background-color: #7434CA !important; + color: #ffffff !important; + } + } + + .dropdown-header { + color: #7434CA !important; + font-weight: 700 !important; + font-size: 0.8rem !important; + text-transform: uppercase !important; + padding: 8px 20px 4px !important; + } +} + +/* ── Dance Academy Form View Styling (New Student & New Guardian Pages) ── */ +.o_dance_form_view, +.o_form_view { + background-color: transparent !important; + + .o_form_sheet_bg { + background-color: #f7f5fa !important; + padding: 24px 32px !important; + } + + .o_form_sheet { + background-color: #ffffff !important; + border-radius: 16px !important; + box-shadow: 0 10px 30px rgba(27, 19, 44, 0.08) !important; + border: 1px solid rgba(116, 52, 202, 0.12) !important; + padding: 32px 40px !important; + } + + /* Statusbar */ + .o_form_statusbar { + background: #ffffff !important; + border-bottom: 2px solid #ede4f7 !important; + padding: 10px 18px !important; + border-radius: 14px 14px 0 0 !important; + + .o_statusbar_status { + .o_arrow_button { + font-weight: 600 !important; + font-size: 0.85rem !important; + border-radius: 6px !important; + margin-left: 4px !important; + transition: all 0.2s ease !important; + + &.btn-primary, + &.disabled.btn-primary { + background: linear-gradient(135deg, #7434CA 0%, #531C96 100%) !important; + border-color: #7434CA !important; + color: #ffffff !important; + box-shadow: 0 3px 8px rgba(116, 52, 202, 0.25) !important; + } + + &.btn-secondary { + color: #495057 !important; + background-color: #f8f6fc !important; + border: 1px solid #ede4f7 !important; + + &:hover { + color: #7434CA !important; + background-color: #ede4f7 !important; + } + } + } + } + } + + /* Section Cards */ + .o_dance_section_card { + background: #faf8fd; + border: 1px solid #ede6f7; + border-radius: 12px; + padding: 20px 24px 10px; + margin-bottom: 20px; + transition: all 0.2s ease; + + &:hover { + border-color: rgba(116, 52, 202, 0.3); + box-shadow: 0 4px 14px rgba(116, 52, 202, 0.06); + } + } + + .o_dance_section_header { + font-size: 0.95rem; + font-weight: 700; + color: #1B132C; + letter-spacing: 0.4px; + text-transform: uppercase; + padding-bottom: 12px; + margin-bottom: 16px; + border-bottom: 2px solid #e9dff7; + display: flex; + align-items: center; + + i { + color: #7434CA; + font-size: 1.1rem; + } + } + + /* Avatar styling */ + .oe_avatar { + border-radius: 16px !important; + border: 2px dashed #CBB5E8 !important; + background-color: #faf8fd !important; + padding: 6px !important; + transition: all 0.2s ease !important; + + img { + border-radius: 12px !important; + } + + &:hover { + border-color: #7434CA !important; + background-color: #f3ebfa !important; + transform: scale(1.02); + } + } + + /* Title / Name field */ + .oe_title { + h1 input.o_input, + h1 .o_field_char { + font-size: 1.8rem !important; + font-weight: 700 !important; + color: #1B132C !important; + } + } + + /* Field Labels */ + .o_form_label, + label { + color: #3B2A58 !important; + font-weight: 600 !important; + font-size: 0.85rem !important; + letter-spacing: 0.2px !important; + } + + /* Inputs, selects, textareas */ + input.o_input, + textarea.o_input, + select.o_input, + .o_field_widget input, + .o_field_widget textarea, + .o_field_widget select { + border: 1px solid #D9CEE8 !important; + border-radius: 8px !important; + padding: 8px 12px !important; + background-color: #ffffff !important; + color: #171238 !important; + font-size: 0.95rem !important; + transition: all 0.2s ease !important; + + &:focus { + border-color: #7434CA !important; + box-shadow: 0 0 0 3px rgba(116, 52, 202, 0.15) !important; + background-color: #ffffff !important; + outline: none !important; + } + } + + /* Radio buttons & checkboxes */ + input[type="radio"], + input[type="checkbox"] { + accent-color: #7434CA !important; + cursor: pointer; + } + + /* Notebook Tabs */ + .o_notebook .nav-tabs { + border-bottom: 2px solid #ede4f7 !important; + gap: 8px !important; + + .nav-link { + border: none !important; + border-radius: 8px 8px 0 0 !important; + padding: 10px 22px !important; + color: #555 !important; + font-weight: 600 !important; + background: transparent !important; + transition: all 0.2s ease !important; + + &:hover { + color: #7434CA !important; + background-color: #faf8fd !important; + } + + &.active { + background: linear-gradient(135deg, #7434CA 0%, #5B1FA8 100%) !important; + color: #ffffff !important; + box-shadow: 0 4px 12px rgba(116, 52, 202, 0.25) !important; + } + } + } + + /* Chatter Action Buttons */ + .oe_chatter { + .btn-primary, + .o_Chatter_sendButton { + background: linear-gradient(135deg, #7434CA 0%, #531C96 100%) !important; + border-color: #7434CA !important; + color: #ffffff !important; + border-radius: 8px !important; + font-weight: 600 !important; + box-shadow: 0 3px 8px rgba(116, 52, 202, 0.2) !important; + } + + .btn-secondary, + .o_Chatter_logButton, + .o_Chatter_activityButton { + border-color: #D9CEE8 !important; + color: #3B2A58 !important; + border-radius: 8px !important; + font-weight: 600 !important; + + &:hover { + background-color: #faf8fd !important; + border-color: #7434CA !important; + color: #7434CA !important; + } + } + } +} + +/* Control Panel Buttons and Breadcrumbs */ +.o_control_panel { + background: linear-gradient(135deg, #f8f6fc 0%, #ede6f7 100%) !important; + border-bottom: 2px solid #7434CA !important; + padding: 12px 24px !important; + + .breadcrumb-item a { + color: #7434CA !important; + font-weight: 600 !important; + } + + .breadcrumb-item.active { + color: #1B132C !important; + font-weight: 700 !important; + } + + .o_form_button_save, + .btn-primary { + background: linear-gradient(135deg, #7434CA 0%, #531C96 100%) !important; + border-color: #7434CA !important; + color: #ffffff !important; + font-weight: 600 !important; + border-radius: 6px !important; + box-shadow: 0 4px 10px rgba(116, 52, 202, 0.25) !important; + transition: all 0.2s ease !important; + + &:hover { + transform: translateY(-1px) !important; + box-shadow: 0 6px 14px rgba(116, 52, 202, 0.35) !important; + } + } + + .o_form_button_cancel, + .btn-secondary { + border: 1px solid #D9CEE8 !important; + color: #495057 !important; + border-radius: 6px !important; + font-weight: 600 !important; + + &:hover { + background-color: #ede4f7 !important; + color: #7434CA !important; + } + } + + .btn.o_arrow_button { + font-weight: 600 !important; + } +} + +/* Badges */ +.bg-purple-soft { + background-color: #ede4f7 !important; +} +.text-purple { + color: #7434CA !important; +} + +/* ── Global Notebook Tab White Text Contrast (All Form Pages: Students, Guardians, Batches, Events, etc.) ── */ +.o_notebook .nav-tabs .nav-link.active, +.o_notebook .nav-tabs .nav-item.show .nav-link, +.o_notebook .nav-link.active, +.nav-tabs .nav-link.active, +.o_form_view .o_notebook .nav-tabs .nav-link.active, +.o_form_view .nav-tabs .nav-link.active, +div.o_notebook ul.nav-tabs > li.nav-item > a.nav-link.active, +div.o_notebook ul.nav-tabs > li.nav-item > button.nav-link.active, +.o_notebook .nav-tabs .nav-link.active span, +.nav-tabs .nav-link.active span { + background: linear-gradient(135deg, #7434CA 0%, #5B1FA8 100%) !important; + color: #ffffff !important; + font-weight: 700 !important; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.25) !important; + border-color: #7434CA !important; + border-radius: 8px 8px 0 0 !important; + box-shadow: 0 4px 12px rgba(116, 52, 202, 0.3) !important; +} + +.o_notebook .nav-tabs .nav-link:not(.active), +.nav-tabs .nav-link:not(.active) { + color: #495057 !important; + font-weight: 600 !important; + + &:hover { + color: #7434CA !important; + background-color: #faf8fd !important; + } +} diff --git a/addons/dance_student/static/src/students_page/students_page.xml b/addons/dance_student/static/src/students_page/students_page.xml new file mode 100644 index 0000000..a3b0407 --- /dev/null +++ b/addons/dance_student/static/src/students_page/students_page.xml @@ -0,0 +1,150 @@ + + + + + +
+
+ 💃 +
Dance ERP
+
+ +
+
+ Dashboard +
+
+ Students +
+
+ Guardians +
+
+ Batches +
+
+ Enrollments +
+
+
Events & Productions
+
+ Events & Recitals +
+
+ Performances & Costumes +
+
+ Rehearsals +
+
+
+ Enrollment Requests +
+
+ Sessions +
+
+ Leave Requests +
+
+ Attendance +
+
+
PERFORMANCE
+
+ Grade Cards +
+
+ Criteria +
+
+
+
+
+ + +
+ + + + + + + +
+ + +
+
Students Management
+
+ + + +
+
+ +
+
+ +

Loading Students...

+
+ +
+ +

No Students Found

+

Try adjusting your search or create a new student.

+ +
+ + +
+ + + + + + + + + + + + + + + + + +
IDStudent NamePhoneDance StyleLevelStatus
+ +
+ Avatar +
+ +
+
+ +
+ + + + + + +
+
+
+
+ +
+
+
diff --git a/addons/dance_student/views/res_partner_student_views.xml b/addons/dance_student/views/res_partner_student_views.xml index 9b91f10..ad8f503 100644 --- a/addons/dance_student/views/res_partner_student_views.xml +++ b/addons/dance_student/views/res_partner_student_views.xml @@ -77,8 +77,118 @@ - + + Students + dance_student.action_students_dashboard + + + + + dance.student.form + res.partner + +
+
+ +
+ +
+
+ +
+ STUDENT FULL NAME +

+ +

+
+ + + + +
+
+ +
+
+ Dance Academy Profile +
+ + + + + + + + + +
+ +
+
+ Guardian & Emergency Contact +
+ + + + + + + + + + +
+ +
+
+ Personal Details & Address +
+ + + + + + + + + + + Residential Address + +
+ + + + + + +
+
+
+
+ + + + + + + + + +
+
+ + + +
+ +
+
+ + + + Students List res.partner tree,form [('is_student', '=', True)] @@ -86,9 +196,9 @@ + (0, 0, {'view_mode': 'form', 'view_id': ref('view_dance_student_form')})]"/> - + diff --git a/addons/dance_web/__manifest__.py b/addons/dance_web/__manifest__.py index cd8be78..c434319 100644 --- a/addons/dance_web/__manifest__.py +++ b/addons/dance_web/__manifest__.py @@ -23,6 +23,10 @@ "web.assets_frontend": [ "dance_web/static/src/css/dance_web.css", ], + "web.assets_backend": [ + "dance_web/static/src/enrollment_requests_page/enrollment_requests_page.js", + "dance_web/static/src/enrollment_requests_page/enrollment_requests_page.xml", + ], }, "installable": True, "application": False, diff --git a/addons/dance_web/models/__pycache__/res_users.cpython-310.pyc b/addons/dance_web/models/__pycache__/res_users.cpython-310.pyc index 4bc906c..36995c0 100644 Binary files a/addons/dance_web/models/__pycache__/res_users.cpython-310.pyc and b/addons/dance_web/models/__pycache__/res_users.cpython-310.pyc differ diff --git a/addons/dance_web/models/res_users.py b/addons/dance_web/models/res_users.py index a49d112..50a07e3 100644 --- a/addons/dance_web/models/res_users.py +++ b/addons/dance_web/models/res_users.py @@ -30,7 +30,7 @@ class ResUsers(models.Model): ("is_student", "=", True), "|", ("guardian_id", "=", partner.id), - ("guardian_ids", "in", partner.id), + ("guardian_ids", "in", [partner.id]), ] ) ) @@ -41,5 +41,5 @@ class ResUsers(models.Model): if self._dance_is_teacher(): return "/teacher" if self._dance_is_parent(): - return "/parent" + return "/my/home" return False diff --git a/addons/dance_web/static/src/enrollment_requests_page/enrollment_requests_page.js b/addons/dance_web/static/src/enrollment_requests_page/enrollment_requests_page.js new file mode 100644 index 0000000..a802732 --- /dev/null +++ b/addons/dance_web/static/src/enrollment_requests_page/enrollment_requests_page.js @@ -0,0 +1,93 @@ +/** @odoo-module **/ + +import { Component, useState, onWillStart } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { useService } from "@web/core/utils/hooks"; + +export class DanceEnrollmentRequestsPage extends Component { + setup() { + this.orm = useService("orm"); + this.action = useService("action"); + + this.state = useState({ + requests: [], + loading: false, + searchQuery: "" + }); + + onWillStart(async () => { + await this.loadRequests(); + }); + } + + async loadRequests() { + this.state.loading = true; + try { + let domain = [["state", "!=", "converted"], ["state", "!=", "rejected"]]; + if (this.state.searchQuery) { + domain = [ + "&", + ["state", "!=", "converted"], + "|", "|", "|", + ["reference", "ilike", this.state.searchQuery], + ["student_name", "ilike", this.state.searchQuery], + ["guardian_name", "ilike", this.state.searchQuery], + ["guardian_phone", "ilike", this.state.searchQuery] + ]; + } + + this.state.requests = await this.orm.searchRead( + "dance.enrollment.request", + domain, + ["reference", "student_name", "guardian_name", "guardian_phone", "dance_style", "batch_id", "create_date", "state"], + { limit: 50, order: "create_date DESC" } + ); + } catch (e) { + console.error("Error loading enrollment requests:", e); + } finally { + this.state.loading = false; + } + } + + onSearch(ev) { + if (ev.type === "keyup" && ev.key !== "Enter") { + return; + } + this.loadRequests(); + } + + openRequest(requestId) { + this.action.doAction({ + type: "ir.actions.act_window", + res_model: "dance.enrollment.request", + res_id: requestId, + views: [[false, "form"]], + target: "current" + }); + } + + navigate(route) { + const routes = { + dashboard: "dance_reports.dance_school_dashboard_action", + students: { type: "ir.actions.client", name: "Students", tag: "dance_student.action_students_dashboard" }, + guardians: { type: "ir.actions.client", name: "Guardians", tag: "dance_guardian.action_guardians_dashboard" }, + batches: { type: "ir.actions.client", name: "Batches", tag: "dance_batch.action_batches_dashboard" }, + enrollments: { type: "ir.actions.client", name: "Enrollments", tag: "dance_batch.action_enrollments_dashboard" }, + events: { type: "ir.actions.client", name: "Events & Recitals", tag: "dance_event.action_events_dashboard" }, + enrollment_requests: { type: "ir.actions.client", name: "Enrollment Requests", tag: "dance_web.action_enrollment_requests_dashboard" }, + sessions: { type: "ir.actions.client", name: "Sessions", tag: "dance_attendance.action_sessions_dashboard" }, + leave_requests: { type: "ir.actions.act_window", name: "Leave Requests", res_model: "dance.leave.request", views: [[false, "list"], [false, "form"]] }, + attendance: { type: "ir.actions.act_window", name: "Attendance", res_model: "dance.attendance", views: [[false, "list"], [false, "form"]] }, + performance: { type: "ir.actions.client", name: "Performances", tag: "dance_event.action_performances_dashboard" }, + rehearsals: { type: "ir.actions.client", name: "Rehearsals", tag: "dance_event.action_rehearsals_dashboard" } + }; + + if (routes[route]) { + this.action.doAction(routes[route]); + } + } +} + +DanceEnrollmentRequestsPage.template = "dance_web.DanceEnrollmentRequestsPage"; + +registry.category("actions").add("dance_web.action_enrollment_requests_dashboard", DanceEnrollmentRequestsPage); diff --git a/addons/dance_web/static/src/enrollment_requests_page/enrollment_requests_page.xml b/addons/dance_web/static/src/enrollment_requests_page/enrollment_requests_page.xml new file mode 100644 index 0000000..f62c9aa --- /dev/null +++ b/addons/dance_web/static/src/enrollment_requests_page/enrollment_requests_page.xml @@ -0,0 +1,77 @@ + + + +
+ + + +
+
+
Enrollment Requests
+
+ + +
+
+ +
+
+ +

Loading Requests...

+
+ +
+ +

No Enrollment Requests Found

+

No new requests match your search.

+
+ +
+ + + + + + + + + + + + + + + + + + +
ReferenceStudent NameParent / GuardianPhoneDance StylePreferred BatchCreated OnState
+ + + + + + + + + New Request + Contacted + Approved + Converted + Rejected + UNKNOWN + +
+
+
+
+
+
+
diff --git a/addons/dance_web/views/enrollment_request_views.xml b/addons/dance_web/views/enrollment_request_views.xml index 5051564..c9006a5 100644 --- a/addons/dance_web/views/enrollment_request_views.xml +++ b/addons/dance_web/views/enrollment_request_views.xml @@ -120,6 +120,11 @@
+ + Enrollment Requests Dashboard + dance_web.action_enrollment_requests_dashboard + + Enrollment Requests dance.enrollment.request @@ -134,7 +139,7 @@ diff --git a/parent_portal_design.html b/parent_portal_design.html new file mode 100644 index 0000000..6d16814 --- /dev/null +++ b/parent_portal_design.html @@ -0,0 +1,628 @@ + + + + + + Parent Portal - Dance Vibe + + + + + + + + + + + + + + +
+
+ + + + + +
+
+ + +
+
+ +

Student Dashboard

+
+ +
+ + +
+ + +
+
+
+
+
+ +
+
+

test student

+ STU005 +
+
+
+ Active +
+
+ +
+
+
+
Dance Style
+
Bharatanatyam
+
+
+
+
+
Level
+
Beginner
+
+
+
+
+
+ + +
+
+
+
Attendance
+
+ +
+
+
0.0%
+ +
+
+
+ +
+ 1 Session + 1 Absence +
+
+
+ + +
+
+
+
Fees Overview
+
+ +
+
+
1
+

+ Student invoice pending or generated. +

+ +
+
+ +
+ + +
+ + +
+
+

Enrolled Batches

+ 1 Batch +
+
+
+ + + + + + + + + + + + + + + +
Batch NameStatusEnrollment Date
+
+
+ TB +
+ test batch +
+
+ Active + + 2026-09-28 +
+
+
+
+ + +
+
+

Leave Requests

+
+
+
+ + + + + + + + + + + + + + + + + +
FromToReasonStatus
2026-09-292026-09-29nnn + Approved +
+
+
+
+ +
+ +
+
+ + +
+
+
+
+ + +
+
+ +

+ We are a team of passionate people whose goal is to improve everyone's life through disruptive products. We build great products to solve your business problems. +

+
+
+ +
    +
  • Contact us
  • +
  • info@yourcompany.example.com
  • +
  • +1 555-555-5556
  • +
+
+
+
+

Copyright © Company name

+

Powered by odoo - Create a free website

+
+
+
+ + + + +