Diff -- /var/www/vhosts/3dshawn.com/ptmatrix.3dshawn.com/calendar.cgi
Diff

/var/www/vhosts/3dshawn.com/ptmatrix.3dshawn.com/calendar.cgi

added on local at 2026-07-01 12:34:07

Added
+237
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to 5fca8247a16d
Restore this content →
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.
1#!/usr/bin/perl
2
3##################################################################################################################################
4# ______ ____ ____ ______ ______ ____ ___ __ ___ ______ _ __ ____ ____ __ __ _ __ _____ ____
5# / ____// __ \ / __ \ / ____/ / ____// __ \ / | / |/ // ____/| | / // __ \ / __ \ / //_/ | | / /|__ / / __ \
6# / / / / / // / / // __/ / /_ / /_/ // /| | / /|_/ // __/ | | /| / // / / // /_/ // ,< | | / / /_ < / / / /
7# / /___ / /_/ // /_/ // /___ / __/ / _, _// ___ | / / / // /___ | |/ |/ // /_/ // _, _// /| | | |/ / ___/ /_ / /_/ /
8# \____/ \____//_____//_____/ /_/ /_/ |_|/_/ |_|/_/ /_//_____/ |__/|__/ \____//_/ |_|/_/ |_| |___/ /____/(_)\____/
9#
10# - Written by: Shawn Pickering
11# - shawn@shawnpickering.com
12##################################################################################################################################
13use strict;
14use lib '/var/www/vhosts/3dshawn.com/ptmatrix.3dshawn.com';
15
16
17#############################################################
18## Variables
19#############################################################
20
21
22
23############################################################
24# Modules
25############################################################
26#Load the CGI module
27use CGI;
28my $query = new CGI;
29my $form = $query->Vars;
30
31#Load the login module
32use MODS::Login;
33my $login = new MODS::Login;
34
35#Load Urls
36use MODS::PTMatrix::Urls;
37my $getlist = new MODS::PTMatrix::Urls;
38my $url = $getlist->urls();
39
40#Load the template module
41use MODS::Template;
42my $tfile = new MODS::Template;
43
44#Page Wrapper Module
45use MODS::PTMatrix::Wrapper;
46my $load = new MODS::PTMatrix::Wrapper;
47
48#Load the database connect module
49use MODS::DBConnect;
50my $db = new MODS::DBConnect;
51
52#Load the configuration file
53use MODS::PTMatrix::Config;
54my $config = new MODS::PTMatrix::Config;
55my $database_name = $config->settings('database_name');
56
57#Shared PM helpers
58use MODS::PTMatrix::PM;
59
60#For grid generation
61use Time::Local;
62
63#For day-data payload sent to the click-to-overlay handler
64use JSON::PP qw(encode_json);
65
66
67
68#############################################################
69## Get form data
70#############################################################
71#Escapes bad characters
72foreach my $line(keys %$form){
73 $form->{$line} =~ s/[^!-~\s]//g; #Remove non ascii chars
74 $form->{$line} = quotemeta("$form->{$line}");
75}
76
77
78
79############################################################
80# Program Controls
81############################################################
82$|=1;
83my $userinfo = $login->login_verify();
84print qq~Content-type:text/html; Cache-Control:no-cache;\n\n~;
85if(!$userinfo){print qq~<script>window.location="$url->{login}";</script>~;}
86else{&calendar_view;}
87
88
89############################################################
90# Subroutines
91############################################################
92sub calendar_view{
93 my $uid = MODS::PTMatrix::PM::get_user_id($userinfo);
94 my $cid = MODS::PTMatrix::PM::get_company_id($userinfo);
95
96 #Current month / year - default to today, override via ?y= / ?m=
97 my @now = localtime(time);
98 my $y = $form->{y} || ($now[5] + 1900); $y += 0; $y = 2026 if $y < 1970 || $y > 9999;
99 my $m = $form->{m} || ($now[4] + 1); $m += 0; $m = 1 if $m < 1 || $m > 12;
100
101 #Scope = me / team / dept / all - coerced down to what this user actually has access to
102 my $raw_scope = $form->{scope} || 'me';
103 my $st = MODS::PTMatrix::PM::user_scope_tier($userinfo);
104 $raw_scope = 'me' unless $raw_scope =~ /^(me|team|dept|all)$/;
105 $raw_scope = 'me' if $raw_scope eq 'team' && !$st->{has_team};
106 $raw_scope = 'team' if $raw_scope eq 'dept' && !$st->{has_department};
107 $raw_scope = 'me' if $raw_scope eq 'dept' && !$st->{has_team}; #safety net
108 $raw_scope = 'me' if $raw_scope eq 'all' && !$st->{can_company};
109 my $scope = $raw_scope;
110 my @user_dept_ids = @{ $st->{team_dept_ids} };
111 my @user_dept_ids_full = @{ $st->{dept_dept_ids} };
112 my $has_team = $st->{has_team};
113 my $has_department = $st->{has_department};
114 my $is_mgr = $st->{can_company};
115
116 #Walk one month forward / back for the prev / next links
117 my $first = sprintf("%04d-%02d-01", $y, $m);
118 my $next_t = timegm(0,0,12, 1, $m-1, $y-1900) + 32*86400;
119 my @nx = gmtime($next_t);
120 my ($ny, $nm) = ($nx[5]+1900, $nx[4]+1);
121 my $next_first = sprintf("%04d-%02d-01", $ny, $nm);
122
123 my $prev_t = timegm(0,0,12, 1, $m-1, $y-1900) - 2*86400;
124 my @pv = gmtime($prev_t);
125 my ($py, $pm) = ($pv[5]+1900, $pv[4]+1);
126
127 #Pick the SQL WHERE clause to match the chosen scope
128 my $where_me;
129 if($scope eq 'me'){
130 $where_me = "AND (t.assigned_user_id='$uid' OR t.reporter_user_id='$uid')";
131 }elsif($scope eq 'team' || $scope eq 'dept'){
132 my @ids = ($scope eq 'dept') ? @user_dept_ids_full : @user_dept_ids;
133 if(scalar @ids){
134 my $dept_ids = join(',', map { "'$_'" } @ids);
135 $where_me = "AND (t.assigned_department_id IN ($dept_ids) OR t.assigned_user_id IN (SELECT user_id FROM `${database_name}`.department_members WHERE department_id IN ($dept_ids)))";
136 }else{
137 $where_me = "AND 1=0";
138 }
139 }else{
140 $where_me = ''; #scope=all
141 }
142
143 my $dbh = $db->db_connect();
144
145 #------------------------------------------------------------------------------------------------------
146 #Tasks due within the month window
147 my $sql_t = qq~SELECT t.id, t.title, t.priority, t.status, t.due_date, t.start_date, p.id AS project_id, p.name AS project_name, p.key_prefix, p.color AS project_color FROM `${database_name}`.tasks t JOIN `${database_name}`.projects p ON p.id=t.project_id WHERE t.company_id='$cid' AND t.due_date BETWEEN '$first' AND '$next_first' $where_me ORDER BY t.due_date, FIELD(t.priority,'critical','urgent','high','medium','low')~;
148 my @tasks = $db->db_readwrite_multiple($dbh, $sql_t, $ENV{SCRIPT_NAME}, __LINE__);
149 #------------------------------------------------------------------------------------------------------
150
151 #------------------------------------------------------------------------------------------------------
152 #Projects for the "add task" dropdown (only ones still active)
153 my $sql_p = qq~SELECT id, name FROM `${database_name}`.projects WHERE company_id='$cid' AND deleted_at IS NULL AND status NOT IN ('archived','cancelled','complete') ORDER BY name ASC~;
154 my @projects = $db->db_readwrite_multiple($dbh, $sql_p, $ENV{SCRIPT_NAME}, __LINE__);
155 foreach my $p(@projects){$p->{name} = MODS::PTMatrix::PM::h($p->{name});}
156 #------------------------------------------------------------------------------------------------------
157
158 $db->db_disconnect($dbh);
159
160 #Bucket tasks by their due_date
161 my %tasks_by_day;
162 foreach my $t(@tasks){
163 $t->{title} = MODS::PTMatrix::PM::h($t->{title});
164 $t->{project_name} = MODS::PTMatrix::PM::h($t->{project_name} || '');
165 $t->{project_color} ||= '#5aa9ff';
166 push @{$tasks_by_day{$t->{due_date}}}, $t;
167 }
168
169 #Build the 42-cell 6-row grid starting at the previous Sunday
170 my $first_t = timegm(0,0,12, 1, $m-1, $y-1900);
171 my @ft = gmtime($first_t);
172 my $dow_first = $ft[6];
173 my $grid_start = $first_t - $dow_first * 86400;
174
175 my @cells;
176 my %cells_data;
177 my $today = sprintf("%04d-%02d-%02d", $now[5]+1900, $now[4]+1, $now[3]);
178 my $month_label = ('January','February','March','April','May','June','July','August','September','October','November','December')[$m-1];
179 my $cur_d = $grid_start;
180 for(my $i=0; $i<42; $i++){
181 my @x = gmtime($cur_d);
182 my $d_str = sprintf("%04d-%02d-%02d", $x[5]+1900, $x[4]+1, $x[3]);
183 my $in_month = ($x[4]+1 == $m) ? 1 : 0;
184 my $is_today = ($d_str eq $today) ? 1 : 0;
185 my $is_weekend = ($x[6] == 0 || $x[6] == 6) ? 1 : 0;
186 my $tlist = $tasks_by_day{$d_str} || [];
187 my @top = @$tlist > 2 ? @{$tlist}[0..1] : @$tlist;
188 my $more = @$tlist > 2 ? scalar(@$tlist) - 2 : 0;
189 push @cells, {
190 d_str => $d_str,
191 day_num => $x[3],
192 in_month => $in_month,
193 is_today => $is_today,
194 is_weekend => $is_weekend,
195 tasks_top => \@top,
196 more_count => $more,
197 has_tasks => scalar(@$tlist) ? 1 : 0,
198 };
199 $cells_data{$d_str} = { tasks => $tlist };
200 $cur_d += 86400;
201 }
202
203 my $cells_json = encode_json(\%cells_data);
204
205 #Create all template variables
206 my $tvars;
207 $tvars->{y} = $y;
208 $tvars->{m} = $m;
209 $tvars->{month_label} = $month_label;
210 $tvars->{py} = $py;
211 $tvars->{pm} = $pm;
212 $tvars->{ny} = $ny;
213 $tvars->{nm} = $nm;
214 $tvars->{cells} = \@cells;
215 $tvars->{scope_me} = ($scope eq 'me') ? 1 : 0;
216 $tvars->{scope_team} = ($scope eq 'team') ? 1 : 0;
217 $tvars->{scope_dept} = ($scope eq 'dept') ? 1 : 0;
218 $tvars->{scope_all} = ($scope eq 'all') ? 1 : 0;
219 $tvars->{has_department} = $has_department;
220 $tvars->{has_team} = $has_team;
221 $tvars->{is_mgr} = $is_mgr;
222 $tvars->{projects} = \@projects;
223 $tvars->{cells_json} = $cells_json;
224
225 my $body = join('', $tfile->template('tf_calendar.html', $tvars, $userinfo));
226
227 $load->render({
228 userinfo => $userinfo,
229 page_key => 'calendar',
230 title => 'Calendar',
231 body => $body,
232 extra_js => [
233 '/assets/javascript/kanban.js?v=20260610d',
234 '/assets/javascript/kanban-drawer.js?v=20260607a',
235 ],
236 });
237}
Keyboard: j next diff k previous diff g top G bottom r restore c compile-check ? help