bsw@11: execute.view{
bsw@11:   module = "timeline",
bsw@11:   view = "_constants"
bsw@11: }
bsw@11: 
poelzi@144: local active_name = ""
poelzi@145: local areas_ignored = {}
bsw@11: local options_box_count = param.get("options_box_count", atom.number) or 1
bsw@11: if options_box_count > 10 then
bsw@11:   options_box_count = 10
bsw@11: end
bsw@10: 
bsw@10: local function format_dow(dow)
bsw@10:   local dows = {
bsw@10:     _"Monday",
bsw@10:     _"Tuesday",
bsw@10:     _"Wednesday",
bsw@10:     _"Thursday",
bsw@10:     _"Friday",
bsw@10:     _"Saturday",
bsw@10:     _"Sunday"
bsw@10:   }
bsw@10:   return dows[dow+1]
bsw@10: end
bsw@11: slot.put_into("title", _"Timeline")
bsw@10: 
bsw@11: slot.select("actions", function()
bsw@11:   local setting_key = "liquidfeedback_frontend_timeline_current_options"
bsw@11:   local setting = Setting:by_pk(app.session.member.id, setting_key)
bsw@11:   local current_options = ""
bsw@11:   if setting then
bsw@11:     current_options = setting.value
bsw@11:   end
bsw@11:   local setting_maps = app.session.member:get_setting_maps_by_key("timeline_filters")
bsw@11:   for i, setting_map in ipairs(setting_maps) do
bsw@11:     local active
bsw@11:     local options_string = setting_map.value
bsw@11:     local name = setting_map.subkey
bsw@11:     if options_string == current_options then
bsw@11:       active = true
poelzi@144:       active_name = name
bsw@11:     end
bsw@11:     ui.link{
bsw/jbe@19:       image  = { static = "icons/16/time.png" },
bsw/jbe@19:       attr   = { class = active and "action_active" or nil },
bsw/jbe@19:       text   = name,
poelzi@145:       form_attr = { class = "inline" },
bsw@11:       module = 'timeline',
bsw@11:       action = 'update',
bsw@11:       params = {
bsw@11:         options_string = options_string
bsw@11:       },
bsw@11:     }
bsw@11:   end
bsw@11:   if #setting_maps > 0 then
bsw@11:     ui.link{
bsw@11:       content = function()
bsw@11:         ui.image{ static = "icons/16/wrench.png" }
bsw@11:         slot.put(_"Manage filter")
bsw@11:       end,
bsw@11:       module = "timeline",
bsw@11:       view = "list_filter",
bsw@11:     }
bsw@11:   end
bsw@11:   ui.link{
bsw@11:     content = function()
bsw@11:       ui.image{ static = "icons/16/bullet_disk.png" }
bsw@11:       slot.put(_"Save current filter")
bsw@11:     end,
bsw@11:     module = "timeline",
bsw@11:     view = "save_filter",
poelzi@144:     params = {
poelzi@144:       current_name = active_name
poelzi@144:     },
bsw@11:     attr = { 
bsw@11:       onclick = "el=document.getElementById('timeline_save');el.checked=true;el.form.submit();return(false);"
bsw@11:     }
bsw@11:   }
bsw@11: end)
bsw@10: 
bsw@11: util.help("timeline.index", _"Timeline")
bsw@10: 
bsw@10: ui.form{
bsw@10:   module = "timeline",
bsw@11:   action = "update",
bsw@10:   content = function()
bsw/jbe@19:     ui.container{
bsw@11: 
bsw@11:       content = function()
bsw/jbe@19: 
bsw/jbe@19:         ui.tag{
bsw/jbe@19:           tag = "input",
bsw/jbe@19:           attr = {
bsw/jbe@19:             type = "radio",
bsw/jbe@19:             id = "timeline_search_last_24h",
bsw/jbe@19:             name = "search_from",
bsw/jbe@19:             value = "last_24h",
bsw/jbe@19:             checked = param.get("date") == "last_24h" and "checked" or nil
bsw/jbe@19:           },
bsw@11:         }
bsw/jbe@19: 
bsw/jbe@19:         ui.tag{
bsw/jbe@19:           tag = "label",
bsw/jbe@19:           attr = {
bsw/jbe@19:             ["for"] = "timeline_search_last_24h" 
bsw/jbe@19:           },
bsw/jbe@19:           content = " " .. _"last 24 hours" .. " "
bsw/jbe@19:         }
bsw/jbe@19: 
bsw/jbe@19:         ui.tag{
bsw/jbe@19:           tag = "input",
bsw/jbe@19:           attr = {
bsw/jbe@19:             type = "radio",
bsw/jbe@19:             id = "timeline_search_from_date",
bsw/jbe@19:             name = "search_from",
bsw/jbe@19:             value = "date",
bsw/jbe@19:             checked = not (param.get("date") == "last_24h") and "checked" or nil
bsw/jbe@19:           },
bsw@11:         }
bsw@11: 
bsw/jbe@19:         slot.put(" ")
bsw/jbe@19:         local current_date = param.get("date")
bsw/jbe@19:         if not current_date or #current_date == 0 or current_date == "last_24h" then
bsw/jbe@19:           current_date = tostring(db:query("select now()::date as date")[1].date)
bsw/jbe@19:         end
bsw/jbe@19:         ui.tag{
bsw/jbe@19:           tag = "input",
bsw/jbe@19:           attr = {
bsw/jbe@19:             type = "text",
bsw/jbe@19:             id = "timeline_search_date",
bsw/jbe@19:             style = "width: 10em;",
bsw/jbe@19:             onchange = "this.form.submit();",
bsw/jbe@19:             onclick = "document.getElementById('timeline_search_from_date').checked = true;",
bsw/jbe@19:             name = "date",
bsw/jbe@19:             value = current_date
bsw/jbe@19:           },
bsw/jbe@19:           content = function() end
bsw/jbe@19:         }
bsw/jbe@19:     
bsw/jbe@19:         ui.script{ static = "gregor.js/gregor.js" }
poelzi@146:         util.gregor("timeline_search_date", true)
bsw/jbe@19:     
bsw/jbe@19:     
bsw/jbe@19:         ui.link{
bsw/jbe@19:           attr = { style = "margin-left: 1em; font-weight: bold;", onclick = "document.getElementById('timeline_search_date').form.submit();return(false);" },
bsw/jbe@19:           content = function()
bsw/jbe@19:             ui.image{
bsw/jbe@19:               attr = { style = "margin-right: 0.25em;" },
bsw/jbe@19:               static = "icons/16/magnifier.png"
bsw/jbe@19:             }
bsw/jbe@19:             slot.put(_"Search")
bsw/jbe@19:           end,
bsw/jbe@19:           external = "#",
bsw/jbe@19:         }
bsw/jbe@19:         local show_options = param.get("show_options", atom.boolean)
bsw/jbe@19:         ui.link{
bsw/jbe@19:           attr = { style = "margin-left: 1em;", onclick = "el=document.getElementById('timeline_show_options');el.checked=" .. tostring(not show_options) .. ";el.form.submit();return(false);" },
bsw/jbe@19:           content = function()
bsw/jbe@19:             ui.image{
bsw/jbe@19:               attr = { style = "margin-right: 0.25em;" },
bsw/jbe@19:               static = "icons/16/text_list_bullets.png"
bsw/jbe@19:             }
bsw/jbe@19:             slot.put(not show_options and _"Show filter details" or _"Hide filter details")
bsw/jbe@19:           end,
bsw/jbe@19:           external = "#",
bsw/jbe@19:         }
bsw@11: 
bsw/jbe@19:         ui.field.boolean{
bsw/jbe@19:           attr = { id = "timeline_show_options", style = "display: none;", onchange="this.form.submit();" },
bsw/jbe@19:           name = "show_options",
bsw/jbe@19:           value = param.get("show_options", atom.boolean)
bsw/jbe@19:         }
poelzi@144:         ui.hidden_field{ name = "current_name", value = active_name }
bsw/jbe@19:         ui.field.boolean{
bsw/jbe@19:           attr = { id = "timeline_save", style = "display: none;", onchange="this.form.submit();" },
bsw/jbe@19:           name = "save",
bsw/jbe@19:           value = false
bsw/jbe@19:         }
bsw/jbe@19: 
bsw/jbe@19:       end
bsw@10:     }
bsw@11: 
bsw@11:     ui.container{
bsw@11:       attr = { 
bsw@11:         id = "timeline_options_boxes",
bsw@11:         class = "vertical",
bsw@11:         style = not param.get("show_options", atom.boolean) and "display: none;" or nil
bsw@10:       },
bsw@11:       content = function()
bsw@11: 
bsw@11:         local function option_field(event_ident, filter_ident)
bsw@11:           local param_name
bsw@11:           if not filter_ident then
bsw@11:             param_name = "option_" .. event_ident
bsw@11:           else
bsw@11:             param_name = "option_" .. event_ident .. "_" .. filter_ident
bsw@11:           end
bsw@11:           local value = param.get(param_name, atom.boolean)
bsw@11:           ui.field.boolean{
bsw@11:             attr = { id = param_name },
bsw@11:             name = param_name,
bsw@11:             value = value,
bsw@11:           }
bsw@11:         end
bsw@11: 
bsw@11:         local function filter_option_fields(event_ident, filter_idents)
bsw@11: 
bsw@11:           for i, filter_ident in ipairs(filter_idents) do
bsw@11:               slot.put("
")
bsw@11:               option_field(event_ident, filter_ident)
bsw@11:               slot.put(" | ")
bsw@11:               ui.tag{
bsw@11:                 attr = { ["for"] = "option_" .. event_ident .. "_" .. filter_ident },
bsw@11:                 tag = "label",
bsw@11:                 content = filter_names[filter_ident]
bsw@11:               }
bsw@11:               slot.put("  | ")
bsw@11:           end
bsw@11: 
bsw@11:         end
bsw@10: 
bsw@11:         local event_groups = {
bsw@11:           {
bsw@11:             title = _"Issue events",
bsw@11:             event_idents = {
bsw@11:               "issue_created",
bsw@11:               "issue_canceled",
bsw@11:               "issue_accepted",
bsw@11:               "issue_half_frozen",
bsw@11:               "issue_finished_without_voting",
bsw@11:               "issue_voting_started",
bsw@11:               "issue_finished_after_voting",
bsw@11:             },
bsw@11:             filter_idents = {
bsw@11:               "membership",
bsw@11:               "interested"
bsw@11:             }
bsw@11:           },
bsw@11:           {
bsw@11:             title = _"Initiative events",
bsw@11:             event_idents = {
bsw@11:               "initiative_created",
bsw@11:               "initiative_revoked",
bsw@11:               "draft_created",
bsw@11:               "suggestion_created",
bsw@11:             },
bsw@11:             filter_idents = {
bsw@11:               "membership",
bsw@11:               "interested",
bsw@11:               "supporter",
bsw@11:               "potential_supporter",
bsw@11:               "initiator"
bsw@11:             }
bsw@11:           }
bsw@11:         }
bsw@11: 
bsw@11:         slot.put("")
bsw@11: 
bsw@11:         for i_event_group, event_group in ipairs(event_groups) do
bsw@11:           slot.put("")
bsw@11:           slot.put("| ")
bsw@11:           slot.put(event_group.title)
bsw@11:           slot.put(" | ")
bsw@11:           slot.put(_"Show only events which match... (or associtated)")
bsw@11:           slot.put(" | ")
bsw@11:           slot.put("
")
bsw@11:           local event_idents = event_group.event_idents
bsw@11:           for i, event_ident in ipairs(event_idents) do
bsw@11:             slot.put("| ")
bsw@11:             option_field(event_ident)
bsw@11:             slot.put(" | ")
bsw@11:             ui.tag{
bsw@11:               attr = { ["for"] = "option_" .. event_ident },
bsw@11:               tag = "label",
bsw@11:               content = event_names[event_ident]
bsw@11:             }
bsw@11:             slot.put("  | ")
bsw@11:             filter_option_fields(event_ident, event_group.filter_idents)
bsw@11:             slot.put("
")
bsw@11:           end
bsw@11:         end
bsw@11: 
bsw@11:         slot.put("
")
bsw@11: 
poelzi@145:         local areas = Area:new_selector():add_where("active='t'"):exec()
poelzi@145:         for i, area in ipairs(areas) do
poelzi@145:           if param.get("option_ignore_area_"..tostring(area.id)) then
poelzi@145:             areas_ignored[#areas_ignored+1] = area.id
poelzi@145:           end
poelzi@145:         end
poelzi@145: 
poelzi@145:         ui.multiselect{
poelzi@145:           style = "checkbox",
poelzi@145:           selected_ids = areas_ignored, 
poelzi@145:           container_attr = { class = "ignore_area_list" },
poelzi@145:           container2_attr = { class = "ignore_area_item" },
poelzi@145:           label = _"Ignore Areas",
poelzi@145:           name = "option_ignore_area[]",
poelzi@145:           foreign_records = areas,
poelzi@145:           foreign_id      = "id",
poelzi@145:           foreign_name    = "name"
poelzi@145:         }
poelzi@145: 
bsw@11:       end
bsw@10:     }
bsw@10:   end
bsw@10: }
bsw@10: 
bsw@10: local date = param.get("date")
bsw/jbe@19: 
bsw@11: if not date or #date == 0 then
bsw@10:   date = "today"
bsw@10: end
bsw@11: 
bsw@11: local timeline_selector
bsw@11: 
bsw@11: for event, event_name in pairs(event_names) do
bsw@11: 
bsw@11:   if param.get("option_" .. event, atom.boolean) then
bsw@11: 
bsw@11:     local tmp = Timeline:new_selector()
bsw/jbe@19:       if event == "draft_created" then
bsw/jbe@19:         tmp
bsw/jbe@19:           :reset_fields()
bsw/jbe@19:           :add_field("max(timeline.occurrence)", "occurrence")
bsw/jbe@19:           :add_field("timeline.event", nil,  { "grouped" })
bsw/jbe@19:           :add_field("timeline.issue_id", nil, { "grouped" })
bsw@41:           :add_field("draft.initiative_id", nil, { "grouped" })
bsw/jbe@19:           :add_field("max(timeline.draft_id)", "draft_id")
bsw/jbe@19:           :add_field("timeline.suggestion_id", nil, { "grouped" })
bsw/jbe@19:           :add_field("COUNT(*)", "count")
bsw/jbe@19:       else
bsw/jbe@19:         tmp
bsw/jbe@19:           :add_field("1", "count")
bsw/jbe@19:       end
bsw@11: 
bsw/jbe@19:       if date == "last_24h" then
bsw/jbe@19:         tmp:add_where{ "occurrence > now() - '24 hours'::interval" }
bsw/jbe@19:       else
poelzi@146:         local start,stop = string.gmatch(date, "(%d+-%d+-%d+):(%d+-%d+-%d+)")()
poelzi@146:         if start and stop then
poelzi@146:           tmp:add_where{ "occurrence::date >= ?::date AND occurrence::date <= ?::date", start, stop }
poelzi@146:         else
poelzi@146:           local age = string.gmatch(date, "age:(.+)")()
poelzi@146:           if age then
poelzi@146:             tmp:add_where{ "occurrence >= now() - ?::interval", age }
poelzi@146:           else 
poelzi@146:             local since = string.gmatch(date, "since:%s*(%d+-%d+-%d+)%s*")()
poelzi@146:             if since then
poelzi@146:               tmp:add_where{ "occurrence::date >= ?::date", since }
poelzi@146:             else
poelzi@146:               tmp:add_where{ "occurrence::date = ?::date", date }
poelzi@146:             end
poelzi@146:           end
poelzi@146:         end
bsw/jbe@19:       end
bsw/jbe@19:       tmp
bsw/jbe@19:         :left_join("draft", nil, "draft.id = timeline.draft_id")
bsw/jbe@19:         :left_join("suggestion", nil, "suggestion.id = timeline.suggestion_id")
bsw/jbe@19:         :left_join("initiative", nil, "initiative.id = timeline.initiative_id or initiative.id = draft.initiative_id or initiative.id = suggestion.initiative_id")
bsw/jbe@19:         :left_join("issue", nil, "issue.id = timeline.issue_id or issue.id = initiative.issue_id")
bsw/jbe@19:         :left_join("area", nil, "area.id = issue.area_id")
bsw@11: 
bsw/jbe@19:         :left_join("interest", "_interest", { "_interest.issue_id = issue.id AND _interest.member_id = ?", app.session.member.id} )
bsw/jbe@19:         :left_join("initiator", "_initiator", { "_initiator.initiative_id = initiative.id AND _initiator.member_id = ?", app.session.member.id} )
bsw/jbe@19:         :left_join("membership", "_membership", { "_membership.area_id = area.id AND _membership.member_id = ?", app.session.member.id} )
bsw/jbe@19:         :left_join("supporter", "_supporter", { "_supporter.initiative_id = initiative.id AND _supporter.member_id = ?", app.session.member.id} )
bsw/jbe@19: 
bsw/jbe@19:       local group
bsw/jbe@19:       if event == "draft_created" then
bsw/jbe@19:         group = { "grouped" }
poelzi@172:         tmp:add_where{"EXISTS(SELECT 1 from draft as cdraft WHERE draft.initiative_id = cdraft.initiative_id AND cdraft.id != draft.id ORDER BY cdraft.id DESC)"}
bsw/jbe@19:       end
bsw/jbe@19: 
bsw/jbe@19:       tmp
bsw/jbe@19:         :add_field("(_interest.member_id NOTNULL)", "is_interested", group)
bsw/jbe@19:         :add_field("(_initiator.member_id NOTNULL)", "is_initiator", group)
bsw/jbe@19:         :add_field({"(_supporter.member_id NOTNULL) AND NOT EXISTS(SELECT NULL FROM opinion WHERE opinion.initiative_id = initiative.id AND opinion.member_id = ? AND ((opinion.degree = 2 AND NOT fulfilled) OR (opinion.degree = -2 AND fulfilled)) LIMIT 1)", app.session.member.id }, "is_supporter", group)
bsw/jbe@19:         :add_field({"EXISTS(SELECT NULL FROM opinion WHERE opinion.initiative_id = initiative.id AND opinion.member_id = ? AND ((opinion.degree = 2 AND NOT fulfilled) OR (opinion.degree = -2 AND fulfilled)) LIMIT 1)", app.session.member.id }, "is_potential_supporter", group)
bsw/jbe@19:   --    :left_join("member", nil, "member.id = timeline.member_id", group)
bsw/jbe@19: 
poelzi@145:       if #areas_ignored > 0 then
poelzi@145:         tmp:add_where{"area.id NOT IN ($)", areas_ignored}
poelzi@145:       end
bsw@11: 
bsw@11:     tmp:add_where{ "event = ?", event }
bsw@11: 
bsw@11:     local filters = {}
bsw@11:     if param.get("option_" .. event .. "_membership", atom.boolean) then
bsw@11:       filters[#filters+1] = "(timeline.initiative_id ISNULL AND timeline.issue_id ISNULL AND timeline.draft_id ISNULL AND timeline.suggestion_id ISNULL) OR _membership.member_id NOTNULL"
bsw@11:     end
bsw@11: 
bsw@11:     if param.get("option_" .. event .. "_supporter", atom.boolean) then
bsw@11:       filters[#filters+1] = "(timeline.initiative_id ISNULL AND timeline.issue_id ISNULL AND timeline.draft_id ISNULL AND timeline.suggestion_id ISNULL) OR ((_supporter.member_id NOTNULL) AND NOT EXISTS(SELECT NULL FROM opinion WHERE opinion.initiative_id = initiative.id AND opinion.member_id = ? AND ((opinion.degree = 2 AND NOT fulfilled) OR (opinion.degree = -2 AND fulfilled)) LIMIT 1))"
bsw@11:     end
bsw@11: 
bsw@11:     if param.get("option_" .. event .. "_potential_supporter", atom.boolean) then
bsw@11:       filters[#filters+1] = "(timeline.initiative_id ISNULL AND timeline.issue_id ISNULL AND timeline.draft_id ISNULL AND timeline.suggestion_id ISNULL) OR ((_supporter.member_id NOTNULL) AND EXISTS(SELECT NULL FROM opinion WHERE opinion.initiative_id = initiative.id AND opinion.member_id = ? AND ((opinion.degree = 2 AND NOT fulfilled) OR (opinion.degree = -2 AND fulfilled)) LIMIT 1))"
bsw@11:     end
bsw@10: 
bsw@11:     if param.get("option_" .. event .. "_interested", atom.boolean) then
bsw@11:       filters[#filters+1] = "(timeline.initiative_id ISNULL AND timeline.issue_id ISNULL AND timeline.draft_id ISNULL AND timeline.suggestion_id ISNULL) OR _interest.member_id NOTNULL"
bsw@11:     end
bsw@11: 
bsw@11:     if param.get("option_" .. event .. "_initiator", atom.boolean) then
bsw@11:       filters[#filters+1] = "(timeline.initiative_id ISNULL AND timeline.issue_id ISNULL AND timeline.draft_id ISNULL AND timeline.suggestion_id ISNULL) OR _initiator.member_id NOTNULL"
bsw@11:     end
bsw@11: 
bsw@11:     if #filters > 0 then
bsw@11:       local filter_string = "(" .. table.concat(filters, ") OR (") .. ")"
bsw/jbe@19:       tmp:add_where{ filter_string, app.session.member.id, app.session.member.id }
bsw@11:     end
bsw@11:   
bsw@11:     if not timeline_selector then
bsw@11:       timeline_selector = tmp
bsw@11:     else
bsw@11:       timeline_selector:union_all(tmp)
bsw@11:     end
bsw@11:   end
bsw@11: end
bsw@11: 
bsw@11: if timeline_selector then
bsw@11:   
bsw@11:   local initiatives_per_page = param.get("initiatives_per_page", atom.number)
bsw@11:   
bsw@11:   local outer_timeline_selector = db:new_selector()
bsw@11:   outer_timeline_selector._class = Timeline
bsw/jbe@19:   outer_timeline_selector
bsw/jbe@19:     :add_field{ "timeline.*" }
bsw/jbe@19:     :from({"($)", { timeline_selector }}, "timeline" )
bsw/jbe@19:     :add_order_by("occurrence DESC")
bsw/jbe@19: 
bsw@11:   slot.put("
")
bsw@11:   execute.view{
bsw@11:     module = "timeline",
bsw@11:     view = "_list",
bsw@11:     params = {
bsw@11:       timeline_selector = outer_timeline_selector,
bsw@11:       per_page = param.get("per_page", atom.number),
bsw@11:       event_names = event_names,
bsw@11:       initiatives_per_page = initiatives_per_page
bsw@11:     }
bsw@10:   }
bsw@11: 
bsw@11: else
bsw@10: 
bsw@11:   slot.put(_"No events selected to list")
bsw@11: 
bsw@11: end