bsw/jbe@1309: local for_member = param.get ( "for_member", "table" ) bsw/jbe@1309: local for_unit = param.get ( "for_unit", "table" ) bsw/jbe@1309: local for_area = param.get ( "for_area", "table" ) bsw/jbe@1309: local for_issue = param.get ( "for_issue", "table" ) bsw/jbe@1309: local for_initiative = param.get ( "for_initiative", "table" ) bsw/jbe@1309: local for_sidebar = param.get("for_sidebar", atom.boolean) bsw/jbe@1309: local no_filter = param.get ( "no_filter", atom.boolean ) bsw/jbe@1309: local search = param.get ( "search" ) bsw/jbe@1309: bsw/jbe@1309: local limit = 25 bsw/jbe@1309: bsw/jbe@1309: local mode = request.get_param{ name = "mode" } or "issue" bsw/jbe@1309: bsw/jbe@1309: if for_initiative or for_issue or for_member then bsw/jbe@1309: mode = "timeline" bsw/jbe@1309: end bsw/jbe@1309: bsw@1513: if config.single_unit_id then bsw@1513: for_unit = Unit:by_id(config.single_unit_id) bsw@1513: end bsw@1513: bsw/jbe@1309: local selector bsw/jbe@1309: bsw/jbe@1309: if search then bsw/jbe@1309: bsw/jbe@1309: selector = Issue:get_search_selector(search) bsw/jbe@1309: bsw/jbe@1309: bsw/jbe@1309: elseif mode == "timeline" then bsw/jbe@1309: bsw/jbe@1309: local event_max_id = request.get_param_strings()["event_max_id"] bsw/jbe@1309: bsw/jbe@1309: selector = Event:new_selector() bsw/jbe@1309: :add_order_by("event.id DESC") bsw/jbe@1309: :join("issue", nil, "issue.id = event.issue_id") bsw/jbe@1309: :add_field("now() - event.occurrence", "time_ago") bsw/jbe@1309: :limit(limit + 1) bsw/jbe@1309: bsw/jbe@1309: if event_max_id then bsw/jbe@1309: selector:add_where{ "event.id < ?", event_max_id } bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: if for_member then bsw/jbe@1309: selector:add_where{ "event.member_id = ?", for_member.id } bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: if for_initiative then bsw/jbe@1309: selector:add_where{ "event.initiative_id = ?", for_initiative.id } bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: bsw/jbe@1309: elseif mode == "issue" then bsw/jbe@1309: bsw/jbe@1309: selector = Issue:new_selector() bsw/jbe@1309: bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: if for_unit then bsw/jbe@1309: selector:join("area", nil, "area.id = issue.area_id") bsw/jbe@1309: selector:add_where{ "area.unit_id = ?", for_unit.id } bsw/jbe@1309: elseif for_area then bsw/jbe@1309: selector:add_where{ "issue.area_id = ?", for_area.id } bsw/jbe@1309: elseif for_issue then bsw/jbe@1309: selector:add_where{ "issue.id = ?", for_issue.id } bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: if not search and app.session.member_id then bsw/jbe@1309: selector bsw/jbe@1309: :left_join("interest", "_interest", { bsw/jbe@1309: "_interest.issue_id = issue.id AND _interest.member_id = ?", app.session.member.id bsw/jbe@1309: } ) bsw/jbe@1309: :add_field("(_interest.member_id NOTNULL)", "is_interested") bsw/jbe@1309: :left_join("delegating_interest_snapshot", "_delegating_interest", { [[ bsw/jbe@1309: _delegating_interest.issue_id = issue.id AND bsw/jbe@1309: _delegating_interest.member_id = ? AND bsw/jbe@1309: _delegating_interest.snapshot_id = issue.latest_snapshot_id bsw/jbe@1309: ]], app.session.member.id } ) bsw/jbe@1309: :add_field("_delegating_interest.delegate_member_ids[1]", "is_interested_by_delegation_to_member_id") bsw/jbe@1309: :add_field("_delegating_interest.delegate_member_ids[array_upper(_delegating_interest.delegate_member_ids, 1)]", "is_interested_via_member_id") bsw/jbe@1309: :add_field("array_length(_delegating_interest.delegate_member_ids, 1)", "delegation_chain_length") bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: local function doit() bsw/jbe@1309: bsw/jbe@1309: local last_event_id bsw/jbe@1309: bsw/jbe@1309: local items = selector:exec() bsw/jbe@1309: bsw/jbe@1309: local row_class = "sectionRow" bsw/jbe@1309: if for_sidebar then bsw/jbe@1309: row_class = "sidebarRow" bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: if mode == "timeline" then bsw/jbe@1309: local issues = items:load ( "issue" ) bsw/jbe@1309: local initiative = items:load ( "initiative" ) bsw/jbe@1309: items:load ( "suggestion" ) bsw/jbe@1309: items:load ( "member" ) bsw/jbe@1309: issues:load_everything_for_member_id ( app.session.member_id ) bsw/jbe@1309: initiative:load_everything_for_member_id ( app.session.member_id ) bsw/jbe@1309: elseif mode == "issue" then bsw/jbe@1309: items:load_everything_for_member_id ( app.session.member_id ) bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: local last_event_date bsw/jbe@1309: for i, item in ipairs(items) do bsw/jbe@1309: local event bsw/jbe@1309: local issue bsw/jbe@1309: if mode == "timeline" then bsw/jbe@1309: event = item bsw/jbe@1309: issue = item.issue bsw/jbe@1309: elseif mode == "issue" then bsw/jbe@1309: event = {} bsw/jbe@1309: issue = item bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: last_event_id = event.id bsw/jbe@1309: bsw/jbe@1309: local class = "mdl-card mdl-shadow--2dp mdl-card__fullwidth event " .. row_class bsw/jbe@1309: if event.suggestion_id then bsw/jbe@1309: class = class .. " suggestion" bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: ui.container{ attr = { class = class, }, content = function() bsw/jbe@1309: local event_name bsw/jbe@1309: local event_icon bsw/jbe@1309: local negative_event = false bsw/jbe@1309: bsw/jbe@1309: local days_ago_text bsw/jbe@1309: bsw/jbe@1309: if mode == "timeline" then bsw/jbe@1309: event_name = event.event_name bsw/jbe@1309: if event.event == "issue_state_changed" then bsw/jbe@1309: if event.state == "discussion" then bsw/jbe@1309: event_name = _"Discussion started" bsw/jbe@1309: elseif event.state == "verification" then bsw/jbe@1309: event_name = _"Verification started" bsw/jbe@1309: elseif event.state == "voting" then bsw/jbe@1309: event_name = _"Voting started" bsw/jbe@1309: elseif event.state == "finished_with_winner" then bsw/jbe@1309: event_name = event.state_name bsw/jbe@1309: elseif event.state == "finished_without_winner" then bsw/jbe@1309: event_name = event.state_name bsw/jbe@1309: negative_event = true bsw/jbe@1309: else bsw/jbe@1309: event_name = event.state_name bsw/jbe@1309: negative_event = true bsw/jbe@1309: end bsw/jbe@1309: elseif event.event == "initiative_revoked" then bsw/jbe@1309: negative_event = true bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: if event.time_ago == 0 then bsw/jbe@1309: days_ago_text = _("today at #{time}", { time = format.time(event.occurrence) }) bsw/jbe@1309: elseif event.time_ago == 1 then bsw/jbe@1309: days_ago_text = _("yesterday at #{time}", { time = format.time(event.occurrence) }) bsw/jbe@1309: else bsw/jbe@1309: days_ago_text = _("#{interval} ago", { interval = format.interval_text ( event.time_ago ) } ) bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: elseif mode == "issue" then bsw/jbe@1309: local event_icons_map = { bsw/jbe@1309: admission = "bubble_chart", bsw/jbe@1309: discussion = "question_answer", bsw/jbe@1309: verification = "find_in_page", bsw/jbe@1309: voting = "mail", bsw/jbe@1309: finished_with_winner = "gavel", bsw/jbe@1309: finished_without_winner = "gavel", bsw/jbe@1309: canceled = "clear" bsw/jbe@1309: } bsw/jbe@1309: event_icon = event_icons_map[issue.state] or event_icons_map["canceled"] bsw/jbe@1309: event_name = issue.state_name bsw/jbe@1309: if issue.state_time_left:sub(1,1) ~= "-" then bsw/jbe@1309: days_ago_text = _( "#{interval_text} left", { bsw/jbe@1309: interval_text = format.interval_text ( issue.state_time_left ) bsw/jbe@1309: }) bsw/jbe@1309: elseif issue.closed then bsw/jbe@1309: days_ago_text = _( "#{interval_text} ago", { bsw/jbe@1309: interval_text = format.interval_text ( issue.closed_ago ) bsw/jbe@1309: }) bsw/jbe@1309: else bsw/jbe@1309: days_ago_text = _"phase ends soon" bsw/jbe@1309: end bsw/jbe@1309: if issue.closed and not issue.fully_frozen then bsw/jbe@1309: negative_event = true bsw/jbe@1309: end bsw/jbe@1309: if issue.state == "finished_without_winner" bsw/jbe@1309: or issue.state == "canceled_no_initiative_admitted" bsw/jbe@1309: or issue.state == "canceled_by_admin" bsw/jbe@1309: then bsw/jbe@1309: negative_event = true bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: local class= "event_info" bsw/jbe@1309: bsw/jbe@1309: if negative_event then bsw/jbe@1309: class = class .. " negative" bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: if not for_issue and not for_initiative then bsw@1503: ui.container{ attr = { class = "mdl-card__title mdl-card--has-fab mdl-card--border card-issue" }, content = function() bsw@1489: ui.container{ attr = { class = "contextlinks" }, content = function() bsw@1489: if not (config.single_unit_id and config.single_area_id) then bsw@1489: if not config.single_unit_id then bsw@1489: slot.put ( " " ) bsw@1489: ui.link{ bsw@1489: module = "index", view = "index", params = { unit = issue.area.unit_id }, bsw@1489: attr = { class = "unit" }, content = issue.area.unit.name bsw@1489: } bsw@1489: end bsw@1489: if not config.single_area_id then bsw@1490: if not config.single_unit_id then bsw@1490: slot.put(" » ") bsw@1490: end bsw@1489: ui.link{ bsw@1489: module = "index", view = "index", params = { unit = issue.area.unit_id, area = issue.area_id }, bsw@1489: attr = { class = "area" }, content = issue.area.name bsw@1489: } bsw@1489: end bsw/jbe@1309: end bsw@1489: slot.put(" » ") bsw/jbe@1309: ui.link{ bsw/jbe@1309: module = "issue", view = "show", id = issue.id, bsw@1489: attr = { class = "issue" }, content = issue.name bsw@1489: } bsw@1503: -- end } bsw@1503: -- ui.container{ attr = { class = "mdl-card__subtitle-text .mdl-cell--hide-phone" }, content = function() bsw@1503: ui.container{ attr = { class = class, style = "float: right; color: #fff;" }, content = function () bsw/jbe@1309: if event_icon then bsw@1489: ui.tag{ tag = "i", attr = { class = "material-icons", ["aria-hidden"] = "true" }, content = event_icon } bsw/jbe@1309: end bsw/jbe@1309: slot.put(" ") bsw/jbe@1309: ui.tag { content = event_name } bsw/jbe@1309: slot.put(" ") bsw/jbe@1309: ui.tag{ content = "(" .. days_ago_text .. ")" } bsw/jbe@1309: end } bsw/jbe@1309: end } bsw/jbe@1309: if app.session.member and issue.fully_frozen and not issue.closed and not issue.member_info.direct_voted and app.session.member:has_voting_right_for_unit_id(issue.area.unit_id) then bsw/jbe@1309: ui.link { bsw/jbe@1309: attr = { class = "mdl-button mdl-js-button mdl-button--fab mdl-button--colored" , bsw/jbe@1309: style = "position: absolute; right: 20px; bottom: -27px;" bsw/jbe@1309: }, bsw/jbe@1309: module = "vote", view = "list", bsw/jbe@1309: params = { issue_id = issue.id }, bsw/jbe@1309: content = function() bsw/jbe@1309: ui.tag{ tag = "i", attr = { class = "material-icons" }, content = config.voting_icon or "mail_outline" } bsw/jbe@1309: end bsw/jbe@1309: } bsw/jbe@1309: end bsw/jbe@1309: end } bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: if event.suggestion_id then bsw/jbe@1309: ui.container{ attr = { class = "suggestion" }, content = function() bsw/jbe@1309: ui.link{ bsw/jbe@1309: text = format.string(event.suggestion.name, { bsw/jbe@1309: truncate_at = 160, truncate_suffix = true bsw/jbe@1309: }), bsw/jbe@1309: module = "initiative", view = "show", id = event.initiative.id, bsw/jbe@1309: params = { suggestion_id = event.suggestion_id }, bsw/jbe@1309: anchor = "s" .. event.suggestion_id bsw/jbe@1309: } bsw/jbe@1309: end } bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: if not for_initiative and (not for_issue or event.initiative_id) then bsw/jbe@1309: bsw/jbe@1309: ui.container{ attr = { class = "initiative_list" }, content = function() bsw/jbe@1309: if event.initiative_id then bsw/jbe@1309: local initiative = event.initiative bsw/jbe@1309: bsw/jbe@1309: execute.view{ module = "initiative", view = "_list", params = { bsw/jbe@1309: issue = issue, bsw/jbe@1309: initiative = initiative, bsw/jbe@1309: for_event = mode == "timeline" and not (event.state == issue.state) bsw/jbe@1309: bsw/jbe@1309: } } bsw/jbe@1309: else bsw/jbe@1309: local initiatives = issue.initiatives bsw/jbe@1309: execute.view{ module = "initiative", view = "_list", params = { bsw/jbe@1309: issue = issue, bsw/jbe@1309: initiatives = initiatives, bsw/jbe@1309: for_event = mode == "timeline" and not (event.state == issue.state) bsw/jbe@1309: } } bsw/jbe@1309: end bsw/jbe@1309: end } bsw/jbe@1309: end bsw/jbe@1309: if app.session.member_id then bsw/jbe@1309: ui.container{ attr = { class = "mdl-card__actions mdl-card--border" }, content = function() bsw/jbe@1309: execute.view{ bsw/jbe@1309: module = "delegation", view = "_info", params = { bsw/jbe@1309: issue = issue, member = for_member bsw/jbe@1309: } bsw/jbe@1309: } bsw/jbe@1309: end } bsw/jbe@1309: end bsw/jbe@1309: end } bsw/jbe@1309: bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: if mode == "timeline" then bsw/jbe@1309: if for_sidebar then bsw/jbe@1309: ui.container { attr = { class = row_class }, content = function () bsw/jbe@1309: ui.link{ bsw/jbe@1309: attr = { class = "moreLink" }, bsw/jbe@1309: text = _"Show full history", bsw/jbe@1309: module = "initiative", view = "history", id = for_initiative.id bsw/jbe@1309: } bsw/jbe@1309: end } bsw/jbe@1309: elseif #items > limit then bsw/jbe@1309: ui.container { attr = { class = row_class }, content = function () bsw/jbe@1309: local params = request.get_param_strings() bsw/jbe@1309: ui.link{ bsw/jbe@1309: attr = { class = "moreLink" }, bsw/jbe@1309: text = _"Show older events", bsw/jbe@1309: module = request.get_module(), bsw/jbe@1309: view = request.get_view(), bsw/jbe@1309: id = for_unit and for_unit.id or for_area and for_area.id or for_issue and for_issue.id or for_member and for_member.id, bsw/jbe@1309: params = { bsw/jbe@1309: mode = "timeline", bsw/jbe@1309: event_max_id = last_event_id, bsw/jbe@1309: tab = params["tab"], bsw/jbe@1309: phase = params["phase"], bsw/jbe@1309: closed = params["closed"] bsw/jbe@1309: } bsw/jbe@1309: } bsw/jbe@1309: end } bsw/jbe@1309: elseif #items < 1 then bsw/jbe@1309: ui.container { attr = { class = row_class }, content = _"No more events available" } bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: if #items < 1 then bsw/jbe@1309: ui.section( function() bsw/jbe@1309: ui.sectionRow( function() bsw/jbe@1309: ui.container{ content = _"No results for this selection" } bsw/jbe@1309: end ) bsw/jbe@1309: end ) bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: bsw/jbe@1309: local filters = {} bsw/jbe@1309: bsw/jbe@1309: if not for_initiative and not for_issue and not no_filter then bsw/jbe@1309: bsw/jbe@1309: filters = execute.chunk{ bsw/jbe@1309: module = "issue", chunk = "_filters", params = { bsw/jbe@1309: for_events = mode == "timeline" and true or false, bsw/jbe@1309: member = app.session.member, bsw/jbe@1309: for_member = for_member, bsw/jbe@1309: state = for_state, bsw/jbe@1309: for_unit = for_unit and true or false, bsw/jbe@1309: for_area = for_area and true or false bsw/jbe@1309: }} bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: filters.opened = true bsw/jbe@1309: filters.selector = selector bsw/jbe@1309: bsw/jbe@1309: bsw/jbe@1309: local function dotabs() bsw/jbe@1309: slot.select("filter", function() bsw/jbe@1309: ui.container{ attr = { class = "mdl-tabs mdl-js-tabs mdl-js-ripple-effect float-left" }, content = function() bsw/jbe@1309: ui.container{ attr = { class = "mdl-tabs__tab-bar" }, content = function() bsw/jbe@1309: local mode = request.get_param{ name = "mode" } bsw/jbe@1309: local css_active = (not mode or mode == "issue") and " is-active" or "" bsw/jbe@1309: ui.link{ module = request.get_module(), view = request.get_view(), id = request.get_id_string(), content = "Issues", attr = { class = "mdl-tabs__tab" .. css_active } } bsw/jbe@1309: local css_active = mode and " is-active" or " " bsw/jbe@1309: ui.link{ module = request.get_module(), view = request.get_view(), id = request.get_id_string(), params = { mode = "timeline" }, content = "Timeline", attr = { class = "mdl-tabs__tab" .. css_active } } bsw/jbe@1309: ui.link{ module = "member", view = "list", content = "Member", attr = { class = "mdl-tabs__tab" } } bsw/jbe@1309: end } bsw/jbe@1309: end } bsw/jbe@1309: end) bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: bsw/jbe@1309: if mode == "timeline" then bsw/jbe@1309: --dotabs() bsw/jbe@1309: filters.content = function() bsw/jbe@1309: execute.view{ module = "index", view = "_head" } bsw/jbe@1309: doit() bsw/jbe@1309: end bsw/jbe@1309: else bsw/jbe@1309: -- dotabs() bsw/jbe@1309: filters.content = function() bsw/jbe@1309: if config.voting_only then bsw/jbe@1309: local admission_order_field = "filter_issue_order.order_in_unit" bsw/jbe@1309: if for_area then bsw/jbe@1309: admission_order_field = "filter_issue_order.order_in_area" bsw/jbe@1309: end bsw/jbe@1309: selector:left_join ( "issue_order_in_admission_state", "filter_issue_order", "filter_issue_order.id = issue.id" ) bsw/jbe@1309: selector:add_order_by ( "issue.closed DESC NULLS FIRST" ) bsw/jbe@1309: selector:add_order_by ( "issue.accepted ISNULL" ) bsw/jbe@1309: selector:add_order_by ( "CASE WHEN issue.accepted ISNULL THEN NULL ELSE justify_interval(coalesce(issue.fully_frozen + issue.voting_time, issue.half_frozen + issue.verification_time, issue.accepted + issue.discussion_time, issue.created + issue.max_admission_time) - now()) END" ) bsw/jbe@1309: selector:add_order_by ( "CASE WHEN issue.accepted ISNULL THEN " .. admission_order_field .. " ELSE NULL END" ) bsw/jbe@1309: selector:add_order_by ( "id" ) bsw/jbe@1309: end bsw@1493: if not search then bsw@1493: execute.view{ module = "index", view = "_head" } bsw@1493: end bsw/jbe@1309: ui.paginate{ bsw/jbe@1309: selector = selector, bsw/jbe@1309: per_page = 25, bsw/jbe@1309: content = doit bsw/jbe@1309: } bsw/jbe@1309: end bsw/jbe@1309: end bsw/jbe@1309: bsw/jbe@1309: filters.class = "mdl-special-card mdl-card__fullwidth mdl-shadow--2dp" bsw/jbe@1309: bsw@1489: filters.legend = _"Filter issues:" bsw@1489: bsw/jbe@1309: ui.filters(filters) bsw/jbe@1309: bsw/jbe@1309: bsw/jbe@1309: