Browse Source

U dashboard page to view last Przypomnij, Wiadomości

Piotr Labudda 6 năm trước cách đây
mục cha
commit
ea1f392f74

+ 1 - 1
SE/se-lib/Route/Storage/AclUsage.php

@@ -35,7 +35,7 @@ class Route_Storage_AclUsage extends RouteBase {
 				$idTable = $acl->getID();
 				$aclNamespace = $acl->getNamespace();
 				$idDatabase = $acl->getDatabaseID();
-				// DBG::nicePrint($acl, 'Acl: $acl class('.get_class($acl).')');
+				// DBG::log($acl, 'array', 'Acl: $acl class('.get_class($acl).')');
 				$aclXsdFieldTypes = $acl->getXsdTypes();
 				// DBG::nicePrint($aclXsdFieldTypes, 'Acl: $aclXsdFieldTypes');
 			} catch (Exception $e) {

+ 206 - 0
SE/se-lib/Schema/UserMsgsStorageAcl.php

@@ -0,0 +1,206 @@
+<?php
+
+Lib::loadClass('Core_AclSimpleSchemaBase');
+Lib::loadClass('ParseOgcFilter');
+
+class Schema_UserMsgsStorageAcl extends Core_AclSimpleSchemaBase {
+
+	// select m.*
+	//   `ID` int(11) NOT NULL AUTO_INCREMENT,
+	//   `idReplyTo` int(11) NOT NULL DEFAULT '0',
+	//   `idThread` int(11) NOT NULL DEFAULT '0',
+	//   `app_className` varchar(255) DEFAULT NULL,
+	//   `msg` varchar(1000) NOT NULL,
+	//   `msgType` enum('info','danger','warning','success') NOT NULL DEFAULT 'info',
+	//   `uiTargetType` enum('default_db_table','default_db_table_record','after_login','everywhere') NOT NULL,
+	//   `uiTargetName` varchar(255) NOT NULL DEFAULT '',
+	//   `userTargetType` enum('none','everyone','admin','user','group') NOT NULL DEFAULT 'none',
+	//   `userTargetName` varchar(255) NOT NULL DEFAULT '',
+	//   `actionExecutedTime` datetime DEFAULT NULL,
+	//   `actionNotes` varchar(255) NOT NULL DEFAULT '',
+	//   `A_STATUS` enum('WAITING','NORMAL','OFF_HARD','DELETED') NOT NULL DEFAULT 'WAITING',
+	//   `A_RECORD_CREATE_DATE` datetime DEFAULT NULL,
+	//   `A_RECORD_CREATE_AUTHOR` varchar(40) NOT NULL DEFAULT '',
+	//   `A_RECORD_UPDATE_DATE` datetime DEFAULT NULL,
+	//   `A_RECORD_UPDATE_AUTHOR` varchar(40) NOT NULL DEFAULT '',
+	//   `A_RECORD_DELETE_DATE` datetime DEFAULT NULL,
+	//   `A_RECORD_DELETE_AUTHOR` varchar(40) NOT NULL DEFAULT '',
+	//   PRIMARY KEY (`ID`),
+	//   KEY `app_className` (`app_className`),
+	//   KEY `A_STATUS` (`A_STATUS`),
+	//   KEY `A_RECORD_UPDATE_DATE` (`A_RECORD_UPDATE_DATE`)
+	// 			from `CRM_UI_MSGS` m
+	// 			where m.`uiTargetType`='default_db_table_record'
+	// 		--		and m.`uiTargetName`='.'
+	// 					and (
+	// 			m.`userTargetType` in('everyone')
+	// 			or (m.`userTargetType`='user' and m.`userTargetName`='plabudda')
+	// 			or (m.`userTargetType`='group' and m.`userTargetName` in(94,4495,1767,2948,2975,13069,21168,12887,22017,20046,11875,20906,79,22720,26118,26522,26498,26519,26623,26532,26119,26529,26497,27324,289,288,270,13,20540))
+	// 		)
+	// 					and m.`A_STATUS` in('WAITING', 'NORMAL')
+	// 			order by m.`ID` DESC
+	// 			limit 21
+
+	public $_simpleSchema = [
+		'root' => [
+			'@namespace' => 'default_objects/UserMsgs',
+			'ID' => [ '@type' => 'xsd:integer' ],
+			'idReplyTo' => [ '@type' => 'xsd:integer' ],
+			'idThread' => [ '@type' => 'xsd:integer' ],
+			'app_className' => [ '@type' => 'xsd:string' ],
+			'msg' => [ '@type' => 'xsd:string' ],
+			'msgType' => [ '@type' => 'xsd:string' ], // enum('info','danger','warning','success') NOT NULL DEFAULT 'info',
+			'uiTargetType' => [ '@type' => 'xsd:string' ], // enum('default_db_table','default_db_table_record','after_login','everywhere') NOT NULL,
+			'uiTargetName' => [ '@type' => 'xsd:string' ],
+			'userTargetType' => [ '@type' => 'xsd:string' ], // enum('none','everyone','admin','user','group') NOT NULL DEFAULT 'none',
+			'userTargetName' => [ '@type' => 'xsd:string' ],
+			'actionExecutedTime' => [ '@type' => 'xsd:date' ],
+			'actionNotes' => [ '@type' => 'xsd:string' ],
+			'A_STATUS' => [ '@type' => 'xsd:string' ], // enum('WAITING','NORMAL','OFF_HARD','DELETED') NOT NULL DEFAULT 'WAITING',
+			'actionNotes' => [ '@type' => 'xsd:string' ],
+			'autor' => [ '@type' => 'xsd:string', '@alias' => 'A_RECORD_CREATE_AUTHOR' ],
+			'utworzono' => [ '@type' => 'xsd:date', '@alias' => 'A_RECORD_CREATE_DATE' ],
+			'zaktualizował' => [ '@type' => 'xsd:string', '@alias' => 'A_RECORD_UPDATE_AUTHOR' ],
+			'zaktualizowano' => [ '@type' => 'xsd:date', '@alias' => 'A_RECORD_UPDATE_DATE' ],
+			'usunął' => [ '@type' => 'xsd:string', '@alias' => 'A_RECORD_DELETE_AUTHOR' ],
+			'usunięto' => [ '@type' => 'xsd:date', '@alias' => 'A_RECORD_DELETE_DATE' ],
+			// 'custom_field_name' => [ '@type' => 'p5:www_link' ],
+		]
+	];
+	public $_rootTableName = 'CRM_UI_MSGS';
+	public $idUser = null;
+	public $login = null;
+
+	public function __construct($simpleSchema = null) {
+		parent::__construct($simpleSchema);
+		$this->idUser = User::getID(); // default - current user
+		$this->login = User::getLogin();
+	}
+
+	public function setIdUser($idUser) { $this->idUser = intval($idUser); }
+	public function getIdUser() { return $this->idUser; }
+
+	public function getTotal($params = []) {
+		$sqlWhereAnd = $this->_parseSqlWhere($params);
+
+		$idGroupList = $this->_getUserIdGroupList();
+		if (empty($idGroupList)) throw new Exception("Brak przypisanych grup do użytkownika");
+		$sqlIdGroupsCsv = implode(",", $idGroupList);
+
+		return DB::getPDO()->fetchValue("
+			select count(1) as total
+			from `CRM_UI_MSGS` m
+			where m.`uiTargetType` = 'default_db_table_record'
+		--		and m.`uiTargetName` = '.'
+				and (
+					m.`userTargetType` in('everyone')
+					or ( m.`userTargetType`='user' and m.`userTargetName` = :login )
+					or ( m.`userTargetType`='group' and m.`userTargetName` in( {$sqlIdGroupsCsv} ) )
+				)
+				and m.`A_STATUS` in('WAITING', 'NORMAL')
+				{$sqlWhereAnd}
+		", [
+			':login' => $this->login,
+		]);
+	}
+
+	public function _parseSqlWhere($params = []) {
+		$sqlWhereAnd = "";
+		// TODO: parse where/ogc, etc.
+		return $sqlWhereAnd;
+	}
+
+	public function getItems($params = []) {
+		$sqlOrderBy = "";
+		$sqlLimitOffset = "";
+		$sqlWhereAnd = $this->_parseSqlWhere($params);
+
+		$currSortCol = V::get('order_by', 'ID', $params);
+		$currSortFlip = strtolower(V::get('order_dir', 'desc', $params));
+		// TODO: validate $currSortCol is in field list
+		// TODO: validate $currSortFlip ('asc' or 'desc')
+
+		$aliasMap = array();
+		foreach ($this->_simpleSchema['root'] as $key => $field) {
+			if ('@' === substr($key, 0, 1)) continue;
+			$aliasMap[ $key ] = (!empty($field['@alias'])) ? $field['@alias'] : $key;
+		}
+		// TODO: if (!array_key_exists($currSortCol, $aliasMap)) throw new Exception("field name not allowed to sort");
+		$currSortCol = (array_key_exists($currSortCol, $aliasMap)) ? $aliasMap[$currSortCol] : null;
+
+		if (!empty($currSortCol) && ('asc' == $currSortFlip || 'desc' == $currSortFlip)) {
+			$sqlOrderBy = "order by m.`{$currSortCol}` {$currSortFlip}";
+		}
+
+		$limit = V::get('limit', 0, $params);
+		$limit = ($limit < 0) ? 0 : $limit;
+		$offset = V::get('limitstart', 0, $params);
+		$offset = ($offset < 0) ? 0 : $offset;
+		if ($limit > 0) $sqlLimitOffset = "limit {$limit} offset {$offset}";
+
+		$idGroupList = $this->_getUserIdGroupList();
+		if (empty($idGroupList)) throw new Exception("Brak przypisanych grup do użytkownika");
+		$sqlIdGroupsCsv = implode(",", $idGroupList);
+
+		$items = DB::getPDO()->fetchAllByKey("
+			select m.ID
+				, m.idReplyTo
+				, m.idThread
+				, m.app_className
+				, m.msg
+				, m.msgType
+				, m.uiTargetType
+				, m.uiTargetName
+				, m.userTargetType
+				, m.userTargetName
+				, m.actionExecutedTime
+				, m.actionNotes
+				, m.A_STATUS
+				, m.actionNotes
+				, m.A_RECORD_CREATE_AUTHOR as `autor`
+				, m.A_RECORD_CREATE_DATE as `utworzono`
+				, m.A_RECORD_UPDATE_AUTHOR as `zaktualizował`
+				, m.A_RECORD_UPDATE_DATE as `zaktualizowano`
+				, m.A_RECORD_DELETE_AUTHOR as `usunął`
+				, m.A_RECORD_DELETE_DATE as `usunięto`
+			from `CRM_UI_MSGS` m
+			where m.`uiTargetType` = 'default_db_table_record'
+		--		and m.`uiTargetName` = '.'
+				and (
+					m.`userTargetType` in('everyone')
+					or ( m.`userTargetType`='user' and m.`userTargetName` = :login )
+					or ( m.`userTargetType`='group' and m.`userTargetName` in( {$sqlIdGroupsCsv} ) )
+				)
+				and m.`A_STATUS` in('WAITING', 'NORMAL')
+				{$sqlWhereAnd}
+			{$sqlOrderBy}
+			{$sqlLimitOffset}
+		", 'ID', [
+			':login' => $this->login,
+		]);
+		// array_walk($items, function (&$item, $key) {
+		// 	$item['link_uruchom_filtr_procesu'] = Request::getPathUri() . "index.php?FUNCTION_INIT=MENU_SELECT_PROCES&_action=setPermsByProces&id_proces={$item['ID']}";
+		// });
+		return $items;
+	}
+
+	public function _getUserIdGroupList() {
+		return array_map(
+			function ($row) {
+				return $row['ID'];
+			}
+			, DB::getPDO()->fetchAll("
+				select z.ID
+				from `CRM_AUTH_PROFILE` as up
+					left join `CRM_LISTA_ZASOBOW` as z on(z.`ID`=up.`ID_ZASOB`)
+				where
+					up.`REMOTE_ID`='{$this->idUser}'
+					and up.`A_STATUS` in('WAITING', 'NORMAL')
+					and up.`REMOTE_TABLE`='ADMIN_USERS'
+					and z.`ID` is not null
+					and z.`TYPE` in('STANOWISKO','PODMIOT','DZIAL')
+			")
+		);
+	}
+
+}

+ 2 - 2
SE/se-lib/Schema/UserProcessStorageAcl.php

@@ -34,7 +34,7 @@ class Schema_UserProcessStorageAcl extends Core_AclSimpleSchemaBase {
 		$sqlWhereAnd = $this->_parseSqlWhere($params);
 
 		$idGroupList = $this->_getUserIdGroupList();
-		if (empty($idGroupList)) throw new Exception("Brak przyipsanych grup do użytwkonika");
+		if (empty($idGroupList)) throw new Exception("Brak przypisanych grup do użytkownika");
 		$sqlIdGroupCsv = implode(",", $idGroupList);
 
 		return DB::getPDO()->fetchValue("
@@ -86,7 +86,7 @@ class Schema_UserProcessStorageAcl extends Core_AclSimpleSchemaBase {
 		if ($limit > 0) $sqlLimitOffset = "limit {$limit} offset {$offset}";
 
 		$idGroupList = $this->_getUserIdGroupList();
-		if (empty($idGroupList)) throw new Exception("Brak przyipsanych grup do użytwkonika");
+		if (empty($idGroupList)) throw new Exception("Brak przypisanych grup do użytkownika");
 		$sqlIdGroupCsv = implode(",", $idGroupList);
 
 		$items = DB::getPDO()->fetchAllByKey("

+ 51 - 3
SE/se-lib/tmpl/defaultPage.php

@@ -1,4 +1,4 @@
-<div class="container">
+<div class="container" style="padding-top:12px">
 	<?php if (!empty($errMsg)) : ?>
 		<div class="alert alert-danger"><?php echo $errMsg; ?></div>
 	<?php endif; ?>
@@ -18,8 +18,56 @@
 		
 	<?php else : ?>
 
-		<p>Jesteś zalogowany jako <?php echo User::getFullName(); ?></p>
-		
+		<?= UI::h('script', ['src'=>"static/vendor.js?v=71baa97d", 'type'=>"text/javascript"]); ?>
+
+		<div class="container">
+			<div class="row">
+				<div class="col-md-12">
+					<p>Jesteś zalogowany jako <?php echo User::getFullName(); ?></p>
+				</div>
+			</div>
+			<div class="row">
+				<div class="col-md-6">
+					<div class="panel panel-default">
+						<div class="panel-heading">
+							<span>Przypomnienia </span>
+							<a href="index.php?_route=Przypomnij&KTO=<?= User::getLogin(); ?>" class="btn btn-xs btn-link pull-right">wszystkie</a>
+						</div>
+						<div class="panel-body">
+							<div id="p5-dashboard--widget-przypomnij">loading...</div>
+							<?php
+							UI::inlineJS(__FILE__ . '.widget-przypomnij.js', [
+								'NAMESPACE' => "p5_default_objects:Przypomnij", // TODO: UserPrzypomnij
+								'USER_LOGIN' => User::getLogin(),
+								'HTML_NODE_ID' => "p5-dashboard--widget-przypomnij",
+								'VIEW_ALL_LINK' => "index.php?_route=Przypomnij&KTO=" . User::getLogin(),
+							]);
+							?>
+						</div>
+					</div>
+				</div>
+				<div class="col-md-6">
+					<div class="panel panel-default">
+						<div class="panel-heading">
+							<span>Wiadomości </span>
+							<a href="index.php?_route=UserMsgs" class="btn btn-xs btn-link pull-right">wszystkie</a>
+						</div>
+						<div class="panel-body">
+							<div id="p5-dashboard--widget-msgs">loading...</div>
+							<?php
+							UI::inlineJS(__FILE__ . '.widget-msgs.js', [
+								'NAMESPACE' => "p5_default_objects:UserMsgs",
+								'USER_LOGIN' => User::getLogin(),
+								'HTML_NODE_ID' => "p5-dashboard--widget-msgs",
+								'VIEW_ALL_LINK' => "index.php?_route=UserMsgs",
+							]);
+							?>
+						</div>
+					</div>
+				</div>
+			</div>
+		</div>
+
 	<?php endif; ?>
 
 </div>

+ 135 - 0
SE/se-lib/tmpl/defaultPage.php.widget-msgs.js

@@ -0,0 +1,135 @@
+var DBG = DBG || 0;
+var DBG1 = true;
+
+if (!NAMESPACE) throw "Missing NAMESPACE";
+if (!USER_LOGIN) throw "Missing USER_LOGIN";
+if (!HTML_NODE_ID) throw "Missing HTML_NODE_ID";
+if (!VIEW_ALL_LINK) throw "Missing VIEW_ALL_LINK";
+if (!global.p5VendorJs) throw "Missing vendor.js";
+if (!global.p5WFS_GetFeature) throw "Missing p5WFS_GetFeature";
+var REQUEST_LIMIT = (REQUEST_LIMIT) ? REQUEST_LIMIT : 10;
+
+var createReactClass = global.p5VendorJs.createReactClass;
+var h = global.p5VendorJs.React.createElement;
+var ReactDOM = global.p5VendorJs.ReactDOM;
+var p5WFS_GetFeature = global.p5WFS_GetFeature;
+
+var P5UI__WidgetMsgs = createReactClass({
+	getInitialState: function () {
+		return {
+			response: [],
+			isLoading: false,
+		}
+	},
+	componentDidMount: function() {
+		DBG && console.log('DBG:P5UI__WidgetMsgs:componentDidMount');
+		this.setState({ isLoading: true })
+		p5WFS_GetFeature(this.props.namespace, {
+			sortBy: 'L_APPOITMENT_DATE+A',
+			maxFeatures: REQUEST_LIMIT,
+		}).then(this.handleWfsResponse.bind(this))
+		.catch(this.handleWfsError.bind(this))
+	},
+	handleWfsResponse: function (response) {
+		DBG && console.log('DBG:P5UI__WidgetMsgs:handleWfsResponse', { response });
+		this.setState({
+			isLoading: false,
+			response: response,
+		})
+	},
+	handleWfsError: function (error) {
+		DBG && console.log('DBG:P5UI__WidgetMsgs:handleWfsError', { error });
+		this.setState({
+			isLoading: false,
+			errorMsg: '' + error,
+		})
+	},
+
+	renderLoading: function () {
+		return h('p', {}, "loading...");
+	},
+	renderEmpty: function () {
+		if (this.state.errorMsg) return this.renderErrorMsg();
+		return h('p', {}, "Brak danych");
+	},
+	renderErrorMsg: function () {
+		return h('div', { className: "alert alert-danger" }, this.state.errorMsg);
+	},
+	renderRow: function (row) {
+		// A_STATUS: "NORMAL"
+		// ID: "985"
+		// actionExecutedTime: "2019-03-13 15:16:23"
+		// app_className: "TableMsgs"
+		// autor: "..."
+		// idReplyTo: "0"
+		// idThread: "0"
+		// msg: "..."
+		// msgType: "info"
+		// uiTargetName: "IN7_DZIENNIK_KORESP.64417"
+		// uiTargetType: "default_db_table_record"
+		// userTargetType: "everyone"
+		// utworzono: "2019-03-13 14:58:22"
+		// zaktualizowano: "2019-03-13 15:16:23"
+		// zaktualizował: "..."
+		row._read = ('WAITING' != row.A_STATUS);
+		row._readByUser = ('WAITING' != row.A_STATUS);
+		if ('WAITING' == row.A_STATUS && USER_LOGIN == row.autor) {
+			if ('user' == row.userTargetType && USER_LOGIN == row.userTargetName) {
+				row._readByUser = false;
+			} else {
+				row._readByUser = true;
+			}
+		}
+		if (row._read) {
+			if (row.zaktualizowano) row._readDate = row.zaktualizowano;
+			if (row.zaktualizował) row._readBy = row.zaktualizował;
+		}
+
+		return h('div', {
+			className: "message", // message-read
+			'data-messageid': row.ID,
+			style: {
+				borderTop: "1px solid #ddd",
+				padding: "6px 12px",
+				backgroundColor: (row._readByUser) ? "#eee" : "#fff",
+				cursor: "pointer",
+			},
+			onClick: function () {
+				window.location.href = 'index.php?_route=UserMsgs&id=' + row.ID + '&usrLogin=' + USER_LOGIN + '&_task=read'
+			}
+		}, [
+			row.msg,
+			h('div', { className: "text-muted", style: { fontStyle: "italic", fontSize: "x-small" } }, [
+				row.utworzono.substr(0, 10) + " od " + row.autor + " do " + this.renderMsgTo(row),
+			]),
+		]);
+	},
+	renderMsgTo: function (row) {
+		switch (row.userTargetType) {
+			case 'everyone': return "wszystkich";
+			case 'user': return row.userTargetName;
+			case 'group': return "grupy " + row.userTargetName;
+		}
+	},
+	render: function () {
+		DBG && console.log('DBG:P5UI__WidgetMsgs:render', { props: this.props, state: this.state });
+		if (this.state.isLoading) return this.renderLoading();
+		if (!this.state.response.length) return this.renderEmpty();
+		return h('div', {}, [
+			h('div', { style: { border: "1px solid #ddd" } }, this.state.response.map(this.renderRow)),
+			h('div', { style: { border: "1px solid #ddd", padding: "12px", textAlign: "center" } }, [
+				h('a', {
+					href: VIEW_ALL_LINK,
+				}, "przeglądaj wszystkie")
+			]),
+		]);
+	}
+});
+
+ReactDOM.render(
+	h(P5UI__WidgetMsgs, {
+		namespace: NAMESPACE,
+		login: USER_LOGIN,
+	}),
+	document.getElementById(HTML_NODE_ID)
+);

+ 125 - 0
SE/se-lib/tmpl/defaultPage.php.widget-przypomnij.js

@@ -0,0 +1,125 @@
+var DBG = DBG || 0;
+var DBG1 = true;
+
+if (!NAMESPACE) throw "Missing NAMESPACE";
+if (!USER_LOGIN) throw "Missing USER_LOGIN";
+if (!HTML_NODE_ID) throw "Missing HTML_NODE_ID";
+if (!VIEW_ALL_LINK) throw "Missing VIEW_ALL_LINK";
+if (!global.p5VendorJs) throw "Missing vendor.js";
+if (!global.p5WFS_GetFeature) throw "Missing p5WFS_GetFeature";
+var REQUEST_LIMIT = (REQUEST_LIMIT) ? REQUEST_LIMIT : 10;
+
+var createReactClass = global.p5VendorJs.createReactClass;
+var h = global.p5VendorJs.React.createElement;
+var ReactDOM = global.p5VendorJs.ReactDOM;
+var p5WFS_GetFeature = global.p5WFS_GetFeature;
+
+var P5UI__WidgetPrzypomnij = createReactClass({
+	getInitialState: function () {
+		return {
+			response: [],
+			isLoading: false,
+			errorMsg: '',
+		}
+	},
+	componentDidMount: function() {
+		DBG && console.log('DBG:P5UI__WidgetPrzypomnij:componentDidMount');
+		this.setState({ isLoading: true })
+		p5WFS_GetFeature(this.props.namespace, {
+			sortBy: 'L_APPOITMENT_DATE+A',
+			maxFeatures: REQUEST_LIMIT,
+			'ogc:Filter': '<wfs:Query>' + '\n' +
+				'<ogc:Filter>' + '\n' +
+					'<ogc:And>' + '\n' +
+						'<ogc:PropertyIsEqualTo>' + '\n' +
+							'<ogc:PropertyName>' + "L_APPOITMENT_USER" + '</ogc:PropertyName>' + "\n" +
+							'<ogc:Literal>' + this.props.login + '</ogc:Literal>' + "\n" +
+						'</ogc:PropertyIsEqualTo>' + '\n' +
+					'</ogc:And>' + '\n' +
+				'</ogc:Filter>' + '\n' +
+			'</wfs:Query>',
+		}).then(this.handleWfsResponse.bind(this))
+		.catch(this.handleWfsError.bind(this))
+	},
+	handleWfsResponse: function (response) {
+		DBG && console.log('DBG:P5UI__WidgetPrzypomnij:handleWfsResponse', { response });
+		this.setState({
+			isLoading: false,
+			response: response,
+		})
+	},
+	handleWfsError: function (error) {
+		DBG && console.log('DBG:P5UI__WidgetPrzypomnij:handleWfsError', { error });
+		this.setState({
+			isLoading: false,
+			errorMsg: '' + error,
+		})
+	},
+
+	renderLoading: function () {
+		return h('p', {}, "loading...");
+	},
+	renderEmpty: function () {
+		if (this.state.errorMsg) return this.renderErrorMsg();
+		return h('p', {}, "Brak danych");
+	},
+	renderErrorMsg: function () {
+		return h('div', { className: "alert alert-danger" }, this.state.errorMsg);
+	},
+	renderRow: function (row) {
+		// A_STATUS: "WAITING"
+		// L_APPOITMENT_DATE: "2013-02-01 00:00:00"
+		// L_APPOITMENT_INFO: "..."
+		// L_APPOITMENT_USER: "..."
+		// PROJECT__ID: "0"
+		// USER__IS_ACTIVE: "1"
+		// featureDesc: "Przycisk - Zapisz"
+		// featureType: "NARZEDZIE"
+		// feature_id: "CRM_LISTA_ZASOBOW.3954"
+		// namespace: "default_db/CRM_LISTA_ZASOBOW"
+		// primaryKey: "3954"
+
+		return h('tr', {}, [
+			h('td', { style: { whiteSpace: "pre" } }, row['L_APPOITMENT_DATE'].substr(0, 10)),
+			h('td', {}, row['L_APPOITMENT_INFO']),
+			h('td', {}, [
+				h('a', {
+					href: 'index.php?_route=ViewTableAjax&namespace=' + row['namespace'] + '#EDIT/' + row['primaryKey'],
+					className: "btn btn-xs btn-link"
+				}, "Edytuj " + row['primaryKey'])
+			]),
+		]);
+	},
+	render: function () {
+		DBG && console.log('DBG:P5UI__WidgetPrzypomnij:render', { props: this.props, state: this.state });
+		if (this.state.isLoading) return this.renderLoading();
+		if (!this.state.response.length) return this.renderEmpty();
+		return h('table', { className: "table table-condensed table-bordered" }, [
+			h('thead', {}, [
+				h('tr', {}, [
+					h('th', {}, "Termin"),
+					h('th', {}, "Opis"),
+					h('th', {}, "Rekord"),
+				]),
+			]),
+			h('tfoot', {}, [
+				h('tr', {}, [
+					h('td', { colSpan: 3, style: { padding: "12px", textAlign: "center" } }, [
+						h('a', {
+							href: VIEW_ALL_LINK,
+						}, "przeglądaj wszystkie")
+					]),
+				]),
+			]),
+			h('tbody', {}, this.state.response.map(this.renderRow)),
+		]);
+	}
+});
+
+ReactDOM.render(
+	h(P5UI__WidgetPrzypomnij, {
+		namespace: NAMESPACE,
+		login: USER_LOGIN,
+	}),
+	document.getElementById(HTML_NODE_ID)
+);