1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace hipanel\modules\finance\widgets; |
4
|
|
|
|
5
|
|
|
use DateTime; |
6
|
|
|
use hipanel\assets\BootstrapDatetimepickerAsset; |
7
|
|
|
use hipanel\modules\finance\models\proxy\Resource; |
8
|
|
|
use Yii; |
9
|
|
|
use yii\helpers\ArrayHelper; |
10
|
|
|
use yii\helpers\Json; |
11
|
|
|
use yii\web\View; |
12
|
|
|
|
13
|
|
|
class ResourceListViewer extends BaseResourceViewer |
14
|
|
|
{ |
15
|
|
|
public function run(): string |
16
|
|
|
{ |
17
|
|
|
$this->registerJs(); |
18
|
|
|
$this->registerCss(); |
19
|
|
|
|
20
|
|
|
return $this->render('ResourceListViewer', [ |
21
|
|
|
'dataProvider' => $this->dataProvider, |
22
|
|
|
'originalContext' => $this->originalContext, |
23
|
|
|
'originalSearchModel' => $this->originalSearchModel, |
24
|
|
|
'uiModel' => $this->uiModel, |
25
|
|
|
'configurator' => $this->configurator, |
26
|
|
|
'model' => new Resource(), |
27
|
|
|
]); |
28
|
|
|
} |
29
|
|
|
|
30
|
|
|
private function registerJs(): void |
31
|
|
|
{ |
32
|
|
|
BootstrapDatetimepickerAsset::register($this->view); |
33
|
|
|
$request = Yii::$app->request; |
34
|
|
|
$ids = Json::encode(ArrayHelper::getColumn($this->dataProvider->getModels(), 'id')); |
35
|
|
|
$csrf_param = $request->csrfParam; |
36
|
|
|
$csrf_token = $request->csrfToken; |
37
|
|
|
$locale = Yii::$app->language; |
38
|
|
|
$cookieName = 'resource_date_' . $this->originalContext->id; |
39
|
|
|
$cookieOptions = Json::encode([ |
40
|
|
|
'path' => '/' . $request->getPathInfo(), |
41
|
|
|
'expires' => (new DateTime())->modify('+45 minutes')->format(DateTime::RFC850), |
42
|
|
|
'max-age' => 3600, |
43
|
|
|
'samesite' => 'lax', |
44
|
|
|
]); |
45
|
|
|
$this->view->registerJs(/** @lang JavaScript */ <<<"JS" |
46
|
|
|
(() => { |
47
|
|
|
const ids = {$ids}; |
48
|
|
|
const buildRange = momentObj => { |
49
|
|
|
return { |
50
|
|
|
time_from: momentObj ? momentObj.startOf('month').format('YYYY-MM-DD') : '', |
51
|
|
|
time_till: momentObj ? momentObj.endOf('month').format('YYYY-MM-DD') : '' |
52
|
|
|
} |
53
|
|
|
} |
54
|
|
|
const getDate = () => { |
55
|
|
|
const rawDate = getCookie('{$cookieName}'); |
56
|
|
|
|
57
|
|
|
return rawDate ? moment(rawDate) : moment(); |
58
|
|
|
} |
59
|
|
|
const setDate = momentObj => { |
60
|
|
|
setCookie('{$cookieName}', momentObj, {$cookieOptions}); |
61
|
|
|
} |
62
|
|
|
const dateInput = $('input[name="date-range"]'); |
63
|
|
|
const {time_from, time_till} = buildRange(getDate()); |
64
|
|
|
dateInput.datetimepicker({ |
65
|
|
|
date: getDate(), |
66
|
|
|
maxDate: moment(), |
67
|
|
|
locale: '{$locale}', |
68
|
|
|
viewMode: 'months', |
69
|
|
|
format: 'MMMM YYYY' |
70
|
|
|
}); |
71
|
|
|
dateInput.datetimepicker().on('dp.update', evt => { |
72
|
|
|
$('td[data-type]').html('<div class="spinner"><div class="rect1"></div><div class="rect2"></div><div class="rect3"></div><div class="rect4"></div><div class="rect5"></div></div>'); |
73
|
|
|
const date = evt.viewDate; |
74
|
|
|
setDate(date); |
75
|
|
|
fetchResources(ids, date.startOf('month').format('YYYY-MM-DD'), date.endOf('month').format('YYYY-MM-DD')).catch(err => { |
76
|
|
|
console.log(err); |
77
|
|
|
hipanel.notify.error(err.message); |
78
|
|
|
}); |
79
|
|
|
}); |
80
|
|
|
|
81
|
|
|
const fetchResources = async (ids, time_from, time_till) => { |
82
|
|
|
const formData = new FormData(); |
83
|
|
|
formData.append('object_ids', ids); |
84
|
|
|
if (time_from.length !== 0 && time_till.length !== 0) { |
85
|
|
|
formData.append('time_from', time_from); |
86
|
|
|
formData.append('time_till', time_till); |
87
|
|
|
} |
88
|
|
|
formData.append('{$csrf_param}', '{$csrf_token}'); |
89
|
|
|
|
90
|
|
|
try { |
91
|
|
|
const response = await fetch('{$this->fetchResourcesUrl}', { |
92
|
|
|
method: 'POST', |
93
|
|
|
body: formData |
94
|
|
|
}); |
95
|
|
|
const result = await response.json(); |
96
|
|
|
Object.entries(result).forEach(entry => { |
97
|
|
|
const [id, resources] = entry; |
98
|
|
|
Object.entries(resources).forEach(resource => { |
99
|
|
|
const [type, data] = resource; |
100
|
|
|
const cell = document.querySelector('tr[data-key="' + id + '"] > td[data-type="' + type + '"]'); |
101
|
|
|
if (!!cell) { |
102
|
|
|
cell.innerHTML = data.amount; |
103
|
|
|
} |
104
|
|
|
}); |
105
|
|
|
}); |
106
|
|
|
const not_counted = document.createElement('span'); |
107
|
|
|
not_counted.classList.add('text-danger'); |
108
|
|
|
not_counted.appendChild(document.createTextNode('not counted')); |
109
|
|
|
document.querySelectorAll('tr[data-key] .spinner').forEach(node => { |
110
|
|
|
node.parentNode.replaceChild(not_counted.cloneNode(true), node); |
111
|
|
|
}) |
112
|
|
|
} catch (error) { |
113
|
|
|
hipanel.notify.error(error.message); |
114
|
|
|
} |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
fetchResources(ids, time_from, time_till); // run request |
118
|
|
|
|
119
|
|
|
// Cookies |
120
|
|
|
function getCookie(name) { |
121
|
|
|
let matches = document.cookie.match(new RegExp( |
122
|
|
|
"(?:^|; )" + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + "=([^;]*)" |
123
|
|
|
)); |
124
|
|
|
return matches ? decodeURIComponent(matches[1]) : undefined; |
125
|
|
|
} |
126
|
|
|
function setCookie(name, value, options = {}) { |
127
|
|
|
options = { |
128
|
|
|
...options |
129
|
|
|
}; |
130
|
|
|
if (options.expires instanceof Date) { |
131
|
|
|
options.expires = options.expires.toUTCString(); |
132
|
|
|
} |
133
|
|
|
let updatedCookie = encodeURIComponent(name) + "=" + encodeURIComponent(value); |
134
|
|
|
for (let optionKey in options) { |
135
|
|
|
updatedCookie += "; " + optionKey; |
136
|
|
|
let optionValue = options[optionKey]; |
137
|
|
|
if (optionValue !== true) { |
138
|
|
|
updatedCookie += "=" + optionValue; |
139
|
|
|
} |
140
|
|
|
} |
141
|
|
|
document.cookie = updatedCookie; |
142
|
|
|
} |
143
|
|
|
function deleteCookie(name) { |
144
|
|
|
setCookie(name, "", { |
145
|
|
|
'max-age': -1 |
146
|
|
|
}) |
147
|
|
|
} |
148
|
|
|
})(); |
149
|
|
|
JS |
150
|
|
|
, View::POS_READY); |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
private function registerCss() |
154
|
|
|
{ |
155
|
|
|
$this->view->registerCss(<<<CSS |
156
|
|
|
.spinner { |
157
|
|
|
width: 50px; |
158
|
|
|
height: 10px; |
159
|
|
|
text-align: center; |
160
|
|
|
font-size: 10px; |
161
|
|
|
display: inline-block; |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
.spinner > div { |
165
|
|
|
background-color: #b8c7ce; |
166
|
|
|
height: 100%; |
167
|
|
|
width: 6px; |
168
|
|
|
display: inline-block; |
169
|
|
|
margin-right: .1rem; |
170
|
|
|
|
171
|
|
|
-webkit-animation: sk-stretchdelay 1.2s infinite ease-in-out; |
172
|
|
|
animation: sk-stretchdelay 1.2s infinite ease-in-out; |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
.spinner .rect2 { |
176
|
|
|
-webkit-animation-delay: -1.1s; |
177
|
|
|
animation-delay: -1.1s; |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
.spinner .rect3 { |
181
|
|
|
-webkit-animation-delay: -1.0s; |
182
|
|
|
animation-delay: -1.0s; |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
.spinner .rect4 { |
186
|
|
|
-webkit-animation-delay: -0.9s; |
187
|
|
|
animation-delay: -0.9s; |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
.spinner .rect5 { |
191
|
|
|
-webkit-animation-delay: -0.8s; |
192
|
|
|
animation-delay: -0.8s; |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
@-webkit-keyframes sk-stretchdelay { |
196
|
|
|
0%, 40%, 100% { -webkit-transform: scaleY(0.4) } |
197
|
|
|
20% { -webkit-transform: scaleY(1.0) } |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
@keyframes sk-stretchdelay { |
201
|
|
|
0%, 40%, 100% { |
202
|
|
|
transform: scaleY(0.4); |
203
|
|
|
-webkit-transform: scaleY(0.4); |
204
|
|
|
} 20% { |
205
|
|
|
transform: scaleY(1.0); |
206
|
|
|
-webkit-transform: scaleY(1.0); |
207
|
|
|
} |
208
|
|
|
} |
209
|
|
|
CSS |
210
|
|
|
); |
211
|
|
|
} |
212
|
|
|
} |