liquid_feedback_frontend
view app/main/issue/_list.lua @ 1503:64229f002a47
Added support for voting weight
author | bsw |
---|---|
date | Thu Jul 30 23:15:49 2020 +0200 (2020-07-30) |
parents | 6b4deab5160a |
children | 895d327a3cb1 |
line source
1 local for_member = param.get ( "for_member", "table" )
2 local for_unit = param.get ( "for_unit", "table" )
3 local for_area = param.get ( "for_area", "table" )
4 local for_issue = param.get ( "for_issue", "table" )
5 local for_initiative = param.get ( "for_initiative", "table" )
6 local for_sidebar = param.get("for_sidebar", atom.boolean)
7 local no_filter = param.get ( "no_filter", atom.boolean )
8 local search = param.get ( "search" )
10 local limit = 25
12 local mode = request.get_param{ name = "mode" } or "issue"
14 if for_initiative or for_issue or for_member then
15 mode = "timeline"
16 end
18 local selector
20 if search then
22 selector = Issue:get_search_selector(search)
25 elseif mode == "timeline" then
27 local event_max_id = request.get_param_strings()["event_max_id"]
29 selector = Event:new_selector()
30 :add_order_by("event.id DESC")
31 :join("issue", nil, "issue.id = event.issue_id")
32 :add_field("now() - event.occurrence", "time_ago")
33 :limit(limit + 1)
35 if event_max_id then
36 selector:add_where{ "event.id < ?", event_max_id }
37 end
39 if for_member then
40 selector:add_where{ "event.member_id = ?", for_member.id }
41 end
43 if for_initiative then
44 selector:add_where{ "event.initiative_id = ?", for_initiative.id }
45 end
48 elseif mode == "issue" then
50 selector = Issue:new_selector()
52 end
54 if for_unit then
55 selector:join("area", nil, "area.id = issue.area_id")
56 selector:add_where{ "area.unit_id = ?", for_unit.id }
57 elseif for_area then
58 selector:add_where{ "issue.area_id = ?", for_area.id }
59 elseif for_issue then
60 selector:add_where{ "issue.id = ?", for_issue.id }
61 end
63 if not search and app.session.member_id then
64 selector
65 :left_join("interest", "_interest", {
66 "_interest.issue_id = issue.id AND _interest.member_id = ?", app.session.member.id
67 } )
68 :add_field("(_interest.member_id NOTNULL)", "is_interested")
69 :left_join("delegating_interest_snapshot", "_delegating_interest", { [[
70 _delegating_interest.issue_id = issue.id AND
71 _delegating_interest.member_id = ? AND
72 _delegating_interest.snapshot_id = issue.latest_snapshot_id
73 ]], app.session.member.id } )
74 :add_field("_delegating_interest.delegate_member_ids[1]", "is_interested_by_delegation_to_member_id")
75 :add_field("_delegating_interest.delegate_member_ids[array_upper(_delegating_interest.delegate_member_ids, 1)]", "is_interested_via_member_id")
76 :add_field("array_length(_delegating_interest.delegate_member_ids, 1)", "delegation_chain_length")
77 end
79 local function doit()
81 local last_event_id
83 local items = selector:exec()
85 local row_class = "sectionRow"
86 if for_sidebar then
87 row_class = "sidebarRow"
88 end
90 if mode == "timeline" then
91 local issues = items:load ( "issue" )
92 local initiative = items:load ( "initiative" )
93 items:load ( "suggestion" )
94 items:load ( "member" )
95 issues:load_everything_for_member_id ( app.session.member_id )
96 initiative:load_everything_for_member_id ( app.session.member_id )
97 elseif mode == "issue" then
98 items:load_everything_for_member_id ( app.session.member_id )
99 end
101 local last_event_date
102 for i, item in ipairs(items) do
103 local event
104 local issue
105 if mode == "timeline" then
106 event = item
107 issue = item.issue
108 elseif mode == "issue" then
109 event = {}
110 issue = item
111 end
113 last_event_id = event.id
115 local class = "mdl-card mdl-shadow--2dp mdl-card__fullwidth event " .. row_class
116 if event.suggestion_id then
117 class = class .. " suggestion"
118 end
120 ui.container{ attr = { class = class, }, content = function()
121 local event_name
122 local event_icon
123 local negative_event = false
125 local days_ago_text
127 if mode == "timeline" then
128 event_name = event.event_name
129 if event.event == "issue_state_changed" then
130 if event.state == "discussion" then
131 event_name = _"Discussion started"
132 elseif event.state == "verification" then
133 event_name = _"Verification started"
134 elseif event.state == "voting" then
135 event_name = _"Voting started"
136 elseif event.state == "finished_with_winner" then
137 event_name = event.state_name
138 elseif event.state == "finished_without_winner" then
139 event_name = event.state_name
140 negative_event = true
141 else
142 event_name = event.state_name
143 negative_event = true
144 end
145 elseif event.event == "initiative_revoked" then
146 negative_event = true
147 end
149 if event.time_ago == 0 then
150 days_ago_text = _("today at #{time}", { time = format.time(event.occurrence) })
151 elseif event.time_ago == 1 then
152 days_ago_text = _("yesterday at #{time}", { time = format.time(event.occurrence) })
153 else
154 days_ago_text = _("#{interval} ago", { interval = format.interval_text ( event.time_ago ) } )
155 end
157 elseif mode == "issue" then
158 local event_icons_map = {
159 admission = "bubble_chart",
160 discussion = "question_answer",
161 verification = "find_in_page",
162 voting = "mail",
163 finished_with_winner = "gavel",
164 finished_without_winner = "gavel",
165 canceled = "clear"
166 }
167 event_icon = event_icons_map[issue.state] or event_icons_map["canceled"]
168 event_name = issue.state_name
169 if issue.state_time_left:sub(1,1) ~= "-" then
170 days_ago_text = _( "#{interval_text} left", {
171 interval_text = format.interval_text ( issue.state_time_left )
172 })
173 elseif issue.closed then
174 days_ago_text = _( "#{interval_text} ago", {
175 interval_text = format.interval_text ( issue.closed_ago )
176 })
177 else
178 days_ago_text = _"phase ends soon"
179 end
180 if issue.closed and not issue.fully_frozen then
181 negative_event = true
182 end
183 if issue.state == "finished_without_winner"
184 or issue.state == "canceled_no_initiative_admitted"
185 or issue.state == "canceled_by_admin"
186 then
187 negative_event = true
188 end
189 end
191 local class= "event_info"
193 if negative_event then
194 class = class .. " negative"
195 end
197 if not for_issue and not for_initiative then
198 ui.container{ attr = { class = "mdl-card__title mdl-card--has-fab mdl-card--border card-issue" }, content = function()
199 ui.container{ attr = { class = "contextlinks" }, content = function()
200 if not (config.single_unit_id and config.single_area_id) then
201 if not config.single_unit_id then
202 slot.put ( " " )
203 ui.link{
204 module = "index", view = "index", params = { unit = issue.area.unit_id },
205 attr = { class = "unit" }, content = issue.area.unit.name
206 }
207 end
208 if not config.single_area_id then
209 if not config.single_unit_id then
210 slot.put(" » ")
211 end
212 ui.link{
213 module = "index", view = "index", params = { unit = issue.area.unit_id, area = issue.area_id },
214 attr = { class = "area" }, content = issue.area.name
215 }
216 end
217 end
218 slot.put(" » ")
219 ui.link{
220 module = "issue", view = "show", id = issue.id,
221 attr = { class = "issue" }, content = issue.name
222 }
223 -- end }
224 -- ui.container{ attr = { class = "mdl-card__subtitle-text .mdl-cell--hide-phone" }, content = function()
225 ui.container{ attr = { class = class, style = "float: right; color: #fff;" }, content = function ()
226 if event_icon then
227 ui.tag{ tag = "i", attr = { class = "material-icons", ["aria-hidden"] = "true" }, content = event_icon }
228 end
229 slot.put(" ")
230 ui.tag { content = event_name }
231 slot.put(" ")
232 ui.tag{ content = "(" .. days_ago_text .. ")" }
233 end }
234 end }
235 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
236 ui.link {
237 attr = { class = "mdl-button mdl-js-button mdl-button--fab mdl-button--colored" ,
238 style = "position: absolute; right: 20px; bottom: -27px;"
239 },
240 module = "vote", view = "list",
241 params = { issue_id = issue.id },
242 content = function()
243 ui.tag{ tag = "i", attr = { class = "material-icons" }, content = config.voting_icon or "mail_outline" }
244 end
245 }
246 end
247 end }
248 end
250 if event.suggestion_id then
251 ui.container{ attr = { class = "suggestion" }, content = function()
252 ui.link{
253 text = format.string(event.suggestion.name, {
254 truncate_at = 160, truncate_suffix = true
255 }),
256 module = "initiative", view = "show", id = event.initiative.id,
257 params = { suggestion_id = event.suggestion_id },
258 anchor = "s" .. event.suggestion_id
259 }
260 end }
261 end
263 if not for_initiative and (not for_issue or event.initiative_id) then
265 ui.container{ attr = { class = "initiative_list" }, content = function()
266 if event.initiative_id then
267 local initiative = event.initiative
269 execute.view{ module = "initiative", view = "_list", params = {
270 issue = issue,
271 initiative = initiative,
272 for_event = mode == "timeline" and not (event.state == issue.state)
274 } }
275 else
276 local initiatives = issue.initiatives
277 execute.view{ module = "initiative", view = "_list", params = {
278 issue = issue,
279 initiatives = initiatives,
280 for_event = mode == "timeline" and not (event.state == issue.state)
281 } }
282 end
283 end }
284 end
285 if app.session.member_id then
286 ui.container{ attr = { class = "mdl-card__actions mdl-card--border" }, content = function()
287 execute.view{
288 module = "delegation", view = "_info", params = {
289 issue = issue, member = for_member
290 }
291 }
292 end }
293 end
294 end }
296 end
298 if mode == "timeline" then
299 if for_sidebar then
300 ui.container { attr = { class = row_class }, content = function ()
301 ui.link{
302 attr = { class = "moreLink" },
303 text = _"Show full history",
304 module = "initiative", view = "history", id = for_initiative.id
305 }
306 end }
307 elseif #items > limit then
308 ui.container { attr = { class = row_class }, content = function ()
309 local params = request.get_param_strings()
310 ui.link{
311 attr = { class = "moreLink" },
312 text = _"Show older events",
313 module = request.get_module(),
314 view = request.get_view(),
315 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,
316 params = {
317 mode = "timeline",
318 event_max_id = last_event_id,
319 tab = params["tab"],
320 phase = params["phase"],
321 closed = params["closed"]
322 }
323 }
324 end }
325 elseif #items < 1 then
326 ui.container { attr = { class = row_class }, content = _"No more events available" }
327 end
328 end
330 if #items < 1 then
331 ui.section( function()
332 ui.sectionRow( function()
333 ui.container{ content = _"No results for this selection" }
334 end )
335 end )
336 end
339 end
342 local filters = {}
344 if not for_initiative and not for_issue and not no_filter then
346 filters = execute.chunk{
347 module = "issue", chunk = "_filters", params = {
348 for_events = mode == "timeline" and true or false,
349 member = app.session.member,
350 for_member = for_member,
351 state = for_state,
352 for_unit = for_unit and true or false,
353 for_area = for_area and true or false
354 }}
355 end
357 filters.opened = true
358 filters.selector = selector
361 local function dotabs()
362 slot.select("filter", function()
363 ui.container{ attr = { class = "mdl-tabs mdl-js-tabs mdl-js-ripple-effect float-left" }, content = function()
364 ui.container{ attr = { class = "mdl-tabs__tab-bar" }, content = function()
365 local mode = request.get_param{ name = "mode" }
366 local css_active = (not mode or mode == "issue") and " is-active" or ""
367 ui.link{ module = request.get_module(), view = request.get_view(), id = request.get_id_string(), content = "Issues", attr = { class = "mdl-tabs__tab" .. css_active } }
368 local css_active = mode and " is-active" or " "
369 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 } }
370 ui.link{ module = "member", view = "list", content = "Member", attr = { class = "mdl-tabs__tab" } }
371 end }
372 end }
373 end)
374 end
377 if mode == "timeline" then
378 --dotabs()
379 filters.content = function()
380 execute.view{ module = "index", view = "_head" }
381 doit()
382 end
383 else
384 -- dotabs()
385 filters.content = function()
386 if config.voting_only then
387 local admission_order_field = "filter_issue_order.order_in_unit"
388 if for_area then
389 admission_order_field = "filter_issue_order.order_in_area"
390 end
391 selector:left_join ( "issue_order_in_admission_state", "filter_issue_order", "filter_issue_order.id = issue.id" )
392 selector:add_order_by ( "issue.closed DESC NULLS FIRST" )
393 selector:add_order_by ( "issue.accepted ISNULL" )
394 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" )
395 selector:add_order_by ( "CASE WHEN issue.accepted ISNULL THEN " .. admission_order_field .. " ELSE NULL END" )
396 selector:add_order_by ( "id" )
397 end
398 if not search then
399 execute.view{ module = "index", view = "_head" }
400 end
401 ui.paginate{
402 selector = selector,
403 per_page = 25,
404 content = doit
405 }
406 end
407 end
409 filters.class = "mdl-special-card mdl-card__fullwidth mdl-shadow--2dp"
411 filters.legend = _"Filter issues:"
413 ui.filters(filters)