diff options
Diffstat (limited to 'addons/project/static/src')
21 files changed, 604 insertions, 0 deletions
diff --git a/addons/project/static/src/css/project.css b/addons/project/static/src/css/project.css new file mode 100644 index 00000000..a88d7d1b --- /dev/null +++ b/addons/project/static/src/css/project.css @@ -0,0 +1,76 @@ + +.oe_kanban_project_avatars img { + width: 30px; + border-radius: 2px; + -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); + -box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); +} +.oe_form_gantt_avatars:after { + font-family: "mnmliconsRegular" !important; + font-size: 21px; + font-weight: 300 !important; + content: "y"; + top: 3px; + position: relative; +} + +.openerp .oe_kanban_view .oe_kanban_project { + width: 250px; + min-height: 160px !important; + cursor: default; +} + +.openerp .oe_percent strong:after { + content: "%"; +} + +.openerp .oe_margin_top_8 { + margin-top: 8px; +} +.openerp .oe_kanban_project .oe_kanban_project_list .col-lg-6 a{ + margin-left: 5px; +} + +/* Kanban status as label in project stage form view */ +.openerp label.oe_project_kanban_legend { + min-width: inherit !important; + margin-top: 6px; + margin-right: 8px; +} + +.o_form_project_tasks.o_form_editable .oe_title { + max-width: initial; +} + +.o_form_project_tasks.o_form_editable .oe_title .o_task_name { + /* + * should be (coming from addons/web/static/src/scss/form_view.scss): + * max-width: map-get($container-max-widths, md) - (2 * $o-horizontal-padding); + */ + max-width: 688px; + margin-right: auto; +} + +.o_kanban_project_tasks .badge { + background: inherit; + color: inherit; + border: 1px solid var(--success); +} + +.o_kanban_project_tasks .badge-warning { + border-color: var(--warning); +} + +.o_kanban_project_tasks .badge-danger { + border-color: var(--danger); +} + +.o_form_project_tasks .ribbon:not(.o_invisible_modifier) + div > h1 > div[name="kanban_state"] { + margin-right: 150px; + padding-left: 1rem; +} + +.o_form_project_recurrence_message *:last-child { + margin-bottom: 0; +} diff --git a/addons/project/static/src/img/app_store.png b/addons/project/static/src/img/app_store.png Binary files differnew file mode 100644 index 00000000..4d79d760 --- /dev/null +++ b/addons/project/static/src/img/app_store.png diff --git a/addons/project/static/src/img/bird.jpg b/addons/project/static/src/img/bird.jpg Binary files differnew file mode 100644 index 00000000..39e96100 --- /dev/null +++ b/addons/project/static/src/img/bird.jpg diff --git a/addons/project/static/src/img/chrome_store.png b/addons/project/static/src/img/chrome_store.png Binary files differnew file mode 100644 index 00000000..95632ed5 --- /dev/null +++ b/addons/project/static/src/img/chrome_store.png diff --git a/addons/project/static/src/img/planner_icon.png b/addons/project/static/src/img/planner_icon.png Binary files differnew file mode 100644 index 00000000..6d6d7c94 --- /dev/null +++ b/addons/project/static/src/img/planner_icon.png diff --git a/addons/project/static/src/img/play_store.png b/addons/project/static/src/img/play_store.png Binary files differnew file mode 100644 index 00000000..73dd393c --- /dev/null +++ b/addons/project/static/src/img/play_store.png diff --git a/addons/project/static/src/img/project-custom-tasks.gif b/addons/project/static/src/img/project-custom-tasks.gif Binary files differnew file mode 100644 index 00000000..e63af1ff --- /dev/null +++ b/addons/project/static/src/img/project-custom-tasks.gif diff --git a/addons/project/static/src/img/tasks_icon.png b/addons/project/static/src/img/tasks_icon.png Binary files differnew file mode 100644 index 00000000..5c5073b1 --- /dev/null +++ b/addons/project/static/src/img/tasks_icon.png diff --git a/addons/project/static/src/img/top_left_arrow.png b/addons/project/static/src/img/top_left_arrow.png Binary files differnew file mode 100644 index 00000000..923b414b --- /dev/null +++ b/addons/project/static/src/img/top_left_arrow.png diff --git a/addons/project/static/src/img/web_planner_email.png b/addons/project/static/src/img/web_planner_email.png Binary files differnew file mode 100644 index 00000000..971a26fe --- /dev/null +++ b/addons/project/static/src/img/web_planner_email.png diff --git a/addons/project/static/src/img/web_planner_project.png b/addons/project/static/src/img/web_planner_project.png Binary files differnew file mode 100644 index 00000000..14db9ea0 --- /dev/null +++ b/addons/project/static/src/img/web_planner_project.png diff --git a/addons/project/static/src/img/web_planner_subtype.png b/addons/project/static/src/img/web_planner_subtype.png Binary files differnew file mode 100644 index 00000000..1dc027c1 --- /dev/null +++ b/addons/project/static/src/img/web_planner_subtype.png diff --git a/addons/project/static/src/js/portal_rating.js b/addons/project/static/src/js/portal_rating.js new file mode 100644 index 00000000..a333b255 --- /dev/null +++ b/addons/project/static/src/js/portal_rating.js @@ -0,0 +1,32 @@ +odoo.define('website_rating_project.rating', function (require) { +'use strict'; + +var time = require('web.time'); +var publicWidget = require('web.public.widget'); + +publicWidget.registry.ProjectRatingImage = publicWidget.Widget.extend({ + selector: '.o_portal_project_rating .o_rating_image', + + /** + * @override + */ + start: function () { + this.$el.popover({ + placement: 'bottom', + trigger: 'hover', + html: true, + content: function () { + var $elem = $(this); + var id = $elem.data('id'); + var ratingDate = $elem.data('rating-date'); + var baseDate = time.auto_str_to_date(ratingDate); + var duration = moment(baseDate).fromNow(); + var $rating = $('#rating_' + id); + $rating.find('.rating_timeduration').text(duration); + return $rating.html(); + }, + }); + return this._super.apply(this, arguments); + }, +}); +}); diff --git a/addons/project/static/src/js/project_calendar.js b/addons/project/static/src/js/project_calendar.js new file mode 100644 index 00000000..51b3488e --- /dev/null +++ b/addons/project/static/src/js/project_calendar.js @@ -0,0 +1,22 @@ +odoo.define('project.ProjectCalendarView', function (require) { +"use strict"; + +const CalendarController = require('web.CalendarController'); +const CalendarView = require('web.CalendarView'); +const viewRegistry = require('web.view_registry'); + +const ProjectCalendarController = CalendarController.extend({ + _renderButtonsParameters() { + return _.extend({}, this._super(...arguments), {scaleDrop: true}); + }, +}); + +const ProjectCalendarView = CalendarView.extend({ + config: _.extend({}, CalendarView.prototype.config, { + Controller: ProjectCalendarController, + }), + }); + +viewRegistry.add('project_calendar', ProjectCalendarView); +return ProjectCalendarView; +}); diff --git a/addons/project/static/src/js/project_kanban.js b/addons/project/static/src/js/project_kanban.js new file mode 100644 index 00000000..41aa7e7b --- /dev/null +++ b/addons/project/static/src/js/project_kanban.js @@ -0,0 +1,71 @@ +odoo.define('project.project_kanban', function (require) { +'use strict'; + +var KanbanController = require('web.KanbanController'); +var KanbanView = require('web.KanbanView'); +var KanbanColumn = require('web.KanbanColumn'); +var view_registry = require('web.view_registry'); +var KanbanRecord = require('web.KanbanRecord'); + +KanbanRecord.include({ + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @override + * @private + */ + // YTI TODO: Should be transformed into a extend and specific to project + _openRecord: function () { + if (this.selectionMode !== true && this.modelName === 'project.project' && + this.$(".o_project_kanban_boxes a").length) { + this.$('.o_project_kanban_boxes a').first().click(); + } else { + this._super.apply(this, arguments); + } + }, +}); + +var ProjectKanbanController = KanbanController.extend({ + custom_events: _.extend({}, KanbanController.prototype.custom_events, { + 'kanban_column_delete_wizard': '_onDeleteColumnWizard', + }), + + _onDeleteColumnWizard: function (ev) { + ev.stopPropagation(); + const self = this; + const column_id = ev.target.id; + var state = this.model.get(this.handle, {raw: true}); + this._rpc({ + model: 'project.task.type', + method: 'unlink_wizard', + args: [column_id], + context: state.getContext(), + }).then(function (res) { + self.do_action(res); + }); + } +}); + +var ProjectKanbanView = KanbanView.extend({ + config: _.extend({}, KanbanView.prototype.config, { + Controller: ProjectKanbanController + }), +}); + +KanbanColumn.include({ + _onDeleteColumn: function (event) { + event.preventDefault(); + if (this.modelName === 'project.task') { + this.trigger_up('kanban_column_delete_wizard'); + return; + } + this._super.apply(this, arguments); + } +}); + +view_registry.add('project_kanban', ProjectKanbanView); + +return ProjectKanbanController; +}); diff --git a/addons/project/static/src/js/project_rating_reporting.js b/addons/project/static/src/js/project_rating_reporting.js new file mode 100644 index 00000000..8e393c67 --- /dev/null +++ b/addons/project/static/src/js/project_rating_reporting.js @@ -0,0 +1,69 @@ +odoo.define('project.project_rating_reporting', function (require) { +'use strict'; + +const core = require('web.core'); +const _t = core._t; + +const viewRegistry = require('web.view_registry'); + +const PivotController = require('web.PivotController'); +const PivotView = require('web.PivotView'); + +const GraphController = require('web.GraphController'); +const GraphView = require('web.GraphView'); + +var ProjectPivotController = PivotController.extend({ + /** + * @override + */ + init: function () { + this._super.apply(this, arguments); + var measures = JSON.parse(JSON.stringify(this.measures)); + if ('res_id' in measures) { + measures.res_id.string = _t('Task'); + } + if ('parent_res_id' in measures) { + measures.parent_res_id.string = _t('Project'); + } + if ('rating' in measures) { + measures.rating.string = _t('Rating Value (/5)'); + } + this.measures = measures; + }, +}); + +var ProjectPivotView = PivotView.extend({ + config: _.extend({}, PivotView.prototype.config, { + Controller: ProjectPivotController, + }), +}); + +viewRegistry.add('project_rating_pivot', ProjectPivotView); + +var ProjectGraphController = GraphController.extend({ + /** + * @override + */ + init: function () { + this._super.apply(this, arguments); + _.each(this.measures, measure => { + if (measure.fieldName === 'res_id') { + measure.description = _t('Task'); + } else if (measure.fieldName === 'parent_res_id') { + measure.description = _t('Project'); + } else if (measure.fieldName === 'rating') { + measure.description = _t('Rating Value (/5)'); + } + }); + }, +}); + +var ProjectGraphView = GraphView.extend({ + config: _.extend({}, GraphView.prototype.config, { + Controller: ProjectGraphController, + }), +}); + +viewRegistry.add('project_rating_graph', ProjectGraphView); + +}); diff --git a/addons/project/static/src/js/project_task_kanban_examples.js b/addons/project/static/src/js/project_task_kanban_examples.js new file mode 100644 index 00000000..9cce29fc --- /dev/null +++ b/addons/project/static/src/js/project_task_kanban_examples.js @@ -0,0 +1,120 @@ +odoo.define('project.task_kanban_examples', function (require) { +'use strict'; + +var core = require('web.core'); +var kanbanExamplesRegistry = require('web.kanban_examples_registry'); + +var _lt = core._lt; + +var greenBullet = '<span class="o_status o_status_green"></span>'; +var redBullet = '<span class="o_status o_status_red"></span>'; +var star = '<a style="color: gold;" class="fa fa-star"/>'; +var clock = '<a class="fa fa-clock-o" />' + +var description_activities = escFormat(_lt('%s Use the %s icon to organize your daily activities.'), '<br/>', clock); +var description = escFormat(_lt('Prioritize Tasks by using the %s icon.'+ + '%s Use the %s button to signalize to your colleagues that a task is ready for the next stage.'+ + '%s Use the %s to signalize a problem or a need for discussion on a task.'+ + '%s'), star, '<br/>', greenBullet, '<br/>', redBullet, description_activities); + +/** + * Helper function to escape a text before formatting it. + * + * First argument is the string to format and the other arguments are the values + * to inject into the string. + * + * Sort of 'lazy escaping' as it is used alongside _lt. + * + * @returns {string} the formatted and escaped string + */ +function escFormat() { + var args = arguments; + return { + toString: function () { + args[0] = _.escape(args[0]); + return _.str.sprintf.apply(_.str, args); + }, + }; +} + +kanbanExamplesRegistry.add('project', { + ghostColumns: [_lt('New'), _lt('Assigned'), _lt('In Progress'), _lt('Done')], + applyExamplesText: _lt("Use This For My Project"), + examples:[{ + name: _lt('Software Development'), + columns: [_lt('Backlog'), _lt('Specifications'), _lt('Development'), _lt('Tests'), _lt('Delivered')], + description: escFormat(_lt('Prioritize Tasks by using the %s icon.'+ + '%s Use the %s button to inform your colleagues that a task is ready for the next stage.'+ + '%s Use the %s to indicate a problem or a need for discussion on a task.'+ + '%s'), star, '<br/>', greenBullet, '<br/>', redBullet, description_activities), + bullets: [greenBullet, redBullet, star], + }, { + name: _lt('Agile Scrum'), + columns: [_lt('Backlog'), _lt('Sprint Backlog'), _lt('Sprint in Progress'), _lt('Sprint Complete'), _lt('Old Completed Sprint')], + description: escFormat(_lt('Waiting for the next stage: use %s and %s bullets. %s'), greenBullet, redBullet, description_activities), + bullets: [greenBullet, redBullet], + }, { + name: _lt('Digital Marketing'), + columns: [_lt('Ideas'), _lt('Researching'), _lt('Writing'), _lt('Editing'), _lt('Done')], + description: escFormat(_lt('Everyone can propose ideas, and the Editor marks the best ones ' + + 'as %s. Attach all documents or links to the task directly, to have all information about ' + + 'a research centralized. %s'), greenBullet, description_activities), + bullets: [greenBullet, redBullet], + }, { + name: _lt('Customer Feedback'), + columns: [_lt('New'), _lt('In development'), _lt('Done'), _lt('Refused')], + description: escFormat(_lt('Customers propose feedbacks by email; Odoo creates tasks ' + + 'automatically, and you can communicate on the task directly. Your managers decide which ' + + 'feedback is accepted %s and which feedback is moved to the %s column. %s'), greenBullet, _lt('"Refused"'), description_activities), + bullets: [greenBullet, redBullet], + }, { + name: _lt('Getting Things Done (GTD)'), + columns: [_lt('Inbox'), _lt('Today'), _lt('This Week'), _lt('This Month'), _lt('Long Term')], + description: escFormat(_lt('Fill your Inbox easily with the email gateway. Periodically review your ' + + 'Inbox and schedule tasks by moving them to others columns. Every day, you review the ' + + '%s column to move important tasks %s. Every Monday, you review the %s column. %s'), _lt('"This Week"'), _lt('"Today"'), _lt('"This Month"'), description_activities), + }, { + name: _lt('Consulting'), + columns: [_lt('New Projects'), _lt('Resources Allocation'), _lt('In Progress'), _lt('Done')], + description: escFormat(_lt('Manage the lifecycle of your project using the kanban view. Add newly acquired projects, assign them and use the %s and %s to define if the project is ready for the next step. %s'), greenBullet, redBullet, description_activities), + bullets: [greenBullet, redBullet], + }, { + name: _lt('Research Project'), + columns: [_lt('Brainstorm'), _lt('Research'), _lt('Draft'), _lt('Final Document')], + description: escFormat(_lt('Handle your idea gathering within Tasks of your new Project and discuss them in the chatter of the tasks. Use the %s and %s to signalize what is the current status of your Idea. %s'), greenBullet, redBullet, description_activities), + bullets: [greenBullet, redBullet], + }, { + name: _lt('Website Redesign'), + columns: [_lt('Page Ideas'), _lt('Copywriting'), _lt('Design'), _lt('Live')], + description: escFormat(_lt('Handle your idea gathering within Tasks of your new Project and discuss them in the chatter of the tasks. Use the %s and %s to signalize what is the current status of your Idea. %s'), greenBullet, redBullet, description_activities), + }, { + name: _lt('T-shirt Printing'), + columns: [_lt('New Orders'), _lt('Logo Design'), _lt('To Print'), _lt('Done')], + description: escFormat(_lt('Communicate with customers on the task using the email gateway. ' + + 'Attach logo designs to the task, so that information flow from designers to the workers ' + + 'who print the t-shirt. Organize priorities amongst orders %s using the icon. %s'), star, description_activities), + bullets: [star], + }, { + name: _lt('Design'), + columns: [_lt('New Request'), _lt('Design'), _lt('Client Review'), _lt('Handoff')], + description: description, + bullets: [greenBullet, redBullet, star, clock], + }, { + name: _lt('Publishing'), + columns: [_lt('Ideas'), _lt('Writing'), _lt('Editing'), _lt('Published')], + description: description, + bullets: [greenBullet, redBullet, star, clock], + }, { + name: _lt('Manufacturing'), + columns: [_lt('New Orders'), _lt('Material Sourcing'), _lt('Manufacturing'), _lt('Assembling'), _lt('Delivered')], + description: description, + bullets: [greenBullet, redBullet, star, clock], + }, { + name: _lt('Podcast and Video Production'), + columns: [_lt('Research'), _lt('Script'), _lt('Recording'), _lt('Mixing'), _lt('Publishing')], + description: description, + bullets: [greenBullet, redBullet, star, clock], + }], +}); + +}); diff --git a/addons/project/static/src/js/tours/project.js b/addons/project/static/src/js/tours/project.js new file mode 100644 index 00000000..df8ef5d7 --- /dev/null +++ b/addons/project/static/src/js/tours/project.js @@ -0,0 +1,112 @@ +odoo.define('project.tour', function(require) { +"use strict"; + +var core = require('web.core'); +var tour = require('web_tour.tour'); + +var _t = core._t; + +tour.register('project_tour', { + sequence: 110, + url: "/web", + rainbowManMessage: "Congratulations, you are now a master of project management.", +}, [tour.stepUtils.showAppsMenuItem(), { + trigger: '.o_app[data-menu-xmlid="project.menu_main_pm"]', + content: _t('Want a better way to <b>manage your projects</b>? <i>It starts here.</i>'), + position: 'right', + edition: 'community', +}, { + trigger: '.o_app[data-menu-xmlid="project.menu_main_pm"]', + content: _t('Want a better way to <b>manage your projects</b>? <i>It starts here.</i>'), + position: 'bottom', + edition: 'enterprise', +}, { + trigger: '.o-kanban-button-new', + extra_trigger: '.o_project_kanban', + content: _t('Let\'s create your first <b>project</b>.'), + position: 'bottom', + width: 200, +}, { + trigger: 'input.o_project_name', + content: _t('Choose a <b>name</b> for your project. <i>It can be anything you want: the name of a customer,\ + of a product, of a team, of a construction site...</i>'), + position: 'right', +}, { + trigger: '.o_open_tasks', + content: _t('Let\'s create your first <b>project</b>.'), + position: 'top', + run: function (actions) { + actions.auto('.modal:visible .btn.btn-primary'); + }, +}, { + trigger: ".o_kanban_project_tasks .o_column_quick_create input", + content: _t("Add columns to organize your tasks into <b>stages</b> <i>e.g. New - In Progress - Done</i>."), + position: 'bottom', +}, { + trigger: ".o_kanban_project_tasks .o_column_quick_create .o_kanban_add", + auto: true, +}, { + trigger: ".o_kanban_project_tasks .o_column_quick_create input", + extra_trigger: '.o_kanban_group', + content: _t("Add columns to organize your tasks into <b>stages</b> <i>e.g. New - In Progress - Done</i>."), + position: 'bottom', +}, { + trigger: ".o_kanban_project_tasks .o_column_quick_create .o_kanban_add", + auto: true, +}, { + trigger: '.o-kanban-button-new', + extra_trigger: '.o_kanban_group:eq(1)', + content: _t("Let's create your first <b>task</b>."), + position: 'bottom', + width: 200, +}, { + trigger: '.o_kanban_quick_create input.o_field_char[name=name]', + extra_trigger: '.o_kanban_project_tasks', + content: _t('Choose a task <b>name</b> <i>(e.g. Website Design, Purchase Goods...)</i>'), + position: 'right', +}, { + trigger: '.o_kanban_quick_create .o_kanban_add', + extra_trigger: '.o_kanban_project_tasks', + content: _t("Add your task once it is ready."), + position: "bottom", +}, { + trigger: ".o_kanban_record .oe_kanban_content", + extra_trigger: '.o_kanban_project_tasks', + content: _t("<b>Drag & drop</b> the card to change your task from stage."), + position: "bottom", + run: "drag_and_drop .o_kanban_group:eq(1) ", +}, { + trigger: ".o_kanban_record:first", + extra_trigger: '.o_kanban_project_tasks', + content: _t("Let's start working on your task."), + position: "bottom", +}, { + trigger: ".o_ChatterTopbar_buttonSendMessage", + content: _t("Use this chatter to <b>send emails</b> and communicate efficently with your customers. \ + Add new people in the followers list to make them aware about the main changes about this task."), + width: 350, + position: "bottom", +}, { + trigger: ".o_ChatterTopbar_buttonLogNote", + content: _t("<b>Log notes</b> for internal communications <i>(the people following this task won't be notified \ + of the note you are logging unless you specifically tag them)</i>. Use @ <b>mentions</b> to ping a colleague \ + or # <b>mentions</b> to reach an entire team."), + width: 350, + position: "bottom" +}, { + trigger: ".o_ChatterTopbar_buttonScheduleActivity", + content: _t("Use <b>activities</b> to organize your daily work."), +}, { + trigger: ".modal-dialog .btn-primary", + content: "Schedule your activity once it is ready", + position: "bottom", + run: "click", +}, { + trigger: ".breadcrumb-item:not(.active):last", + extra_trigger: '.o_form_project_tasks.o_form_readonly', + content: _t("Let's go back to your <b>kanban view</b> to have an overview of your next tasks."), + position: "right", + run: 'click', +}]); + +}); diff --git a/addons/project/static/src/scss/portal_rating.scss b/addons/project/static/src/scss/portal_rating.scss new file mode 100644 index 00000000..4ae3290a --- /dev/null +++ b/addons/project/static/src/scss/portal_rating.scss @@ -0,0 +1,34 @@ +.o_portal_project_rating { + .thumbnail{ + height: 240px; + } + .o_top_partner_rating_image { + height: 15px; + } + .o_top_partner_image { + height: 30px; + width: 30px; + } + .o_top_partner_feedback{ + word-wrap: break-word; + } + .o_vertical_separator { + border-left: 1px solid #eeeeee + } + .o_rating_progress { + margin-bottom: 10px; + } + .o_rating_count { + display: inline-block; + min-width: 22px + } + .o_smiley_no_padding_left { + padding-left: 0; + } + .o_smiley_no_padding_right { + padding-right: 0; + } + .o_lighter_smileys { + opacity: 0.4 + } +}
\ No newline at end of file diff --git a/addons/project/static/src/scss/project_dashboard.scss b/addons/project/static/src/scss/project_dashboard.scss new file mode 100644 index 00000000..73539824 --- /dev/null +++ b/addons/project/static/src/scss/project_dashboard.scss @@ -0,0 +1,51 @@ +.o_kanban_view.o_kanban_dashboard.o_project_kanban { + .o_project_kanban_boxes { + display: flex; + flex-flow: row wrap; + justify-content: space-between; + + .o_project_kanban_box { + position: relative; + text-align: center; + padding: 0 0 0 0; + margin: 0 5px; + + .o_value { + font-weight: 800; + } + + > div { + font-weight: 500; + + button.o_needaction { + font-size: small; + font-weight: 400; + margin-left: 4px; + @include o-hover-opacity(0.5, 1); + + &:before { + content: "/ "; + } + + &:after { + content: "\f086"; + font: normal normal normal 14px/1 FontAwesome; + } + } + } + } + } +} + +.o_dow_widget { + th { + padding: 10px 10px 0 10px; + } + + .o_dow_days { + td { + text-align: center; + vertical-align: middle; + } + } +} diff --git a/addons/project/static/src/xml/project_templates.xml b/addons/project/static/src/xml/project_templates.xml new file mode 100644 index 00000000..f30d8c95 --- /dev/null +++ b/addons/project/static/src/xml/project_templates.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<templates id="template" xml:space="preserve"> + <t t-name="day_of_week_widget"> + <table class="o_dow_widget"> + <thead> + <tr> + <th t-foreach="widget.labels" t-as="label"> + <t t-esc="label"/> + </th> + </tr> + </thead> + <tbody> + <tr class="o_dow_days" /> + </tbody> + </table> + </t> +</templates> |
