浏览代码

MWUI: filter LOG MESSAGE dibatasi 1 hari + search-on-demand

Mohamad Sahlan Umar 2 天之前
父节点
当前提交
25180df514
共有 2 个文件被更改,包括 337 次插入185 次删除
  1. 103 87
      Apps/MWUI/jsp/log/dlog_list.jsp
  2. 234 98
      MWUI/src/id/co/hanoman/log/action/DlogAction.java

+ 103 - 87
Apps/MWUI/jsp/log/dlog_list.jsp

@@ -62,8 +62,24 @@
 				alert(e);
 			}
 		}
+
+		/* ==== TIME FILTER: hanya 1 tanggal yang boleh dipilih (max 1 hari), tapi start time
+		       dan end time (jam:menit) dalam tanggal itu tetap bisa diatur user.
+		       End date selalu disamakan dengan start date (tidak ada input end date terpisah). ==== */
+		function syncTimestampFilter(){
+			var form = document.forms.dlog;
+			form.dlog_timestamp_end_filter.value = form.dlog_timestamp_start_filter.value;
+		}
+
+		/* ==== CR#2: tandai bahwa user benar-benar melakukan aksi (search/page/order/reset),
+		       supaya initial load tidak query ke tabel DLOG ==== */
+		function markSearch(){
+			document.forms.dlog.doSearch.value = "true";
+		}
 		
 		function page(start, count){
+			syncTimestampFilter();
+			markSearch();
 			document.forms.dlog.start.value=start;
 			document.forms.dlog.count.value=count;
 			document.forms.dlog.action.value="list";
@@ -73,6 +89,8 @@
 		}
 
 		function doChangePage(count){
+			syncTimestampFilter();
+			markSearch();
 			document.forms.dlog.start.value= (document.forms.dlog.startx.value-1)*20;
 			if(document.forms.dlog.start.value=="NaN") document.forms.dlog.start.value = 0;
 			document.forms.dlog.count.value=count;
@@ -103,6 +121,9 @@
 		}
 
 		function doFilter(){
+			syncTimestampFilter();
+			markSearch();
+			document.forms.dlog.start.value=0;
 			document.forms.dlog.action.value="list";
 			document.forms.dlog.submit();
 			doDisable(document.forms.dlog, "button");
@@ -111,24 +132,32 @@
 		function doReset(){
 			document.forms.dlog.dlog_type_filter.value="";
 			document.forms.dlog.dlog_source_filter.value="";
+			document.forms.dlog.dlog_source_sma.value="equal";
 			document.forms.dlog.dlog_code_filter.value="";
+			document.forms.dlog.dlog_code_sma.value="equal";
 			document.forms.dlog.dlog_value_filter.value="";
 			document.forms.dlog.dlogMd_label_filter.value="";
+			document.forms.dlog.dlogMd_sma.value="equal";
 			document.forms.dlog.dlog_timestamp_start_filter.value="";
-			document.forms.dlog.dlog_timestamp_hour_start_filter.value="";
-			document.forms.dlog.dlog_timestamp_minute_start_filter.value="";
+			document.forms.dlog.dlog_timestamp_hour_start_filter.value="0";
+			document.forms.dlog.dlog_timestamp_minute_start_filter.value="0";
 			document.forms.dlog.dlog_timestamp_end_filter.value="";
-			document.forms.dlog.dlog_timestamp_hour_end_filter.value="";
-			document.forms.dlog.dlog_timestamp_minute_end_filter.value="";
+			document.forms.dlog.dlog_timestamp_hour_end_filter.value="23";
+			document.forms.dlog.dlog_timestamp_minute_end_filter.value="59";
 			
 			document.forms.dlog.clear.value="true";
 			document.forms.dlog.action.value="list";
+			/* CR#2: reset tetap dianggap aksi user -> boleh query (biar user lihat data ter-reset).
+			   Kalau reset juga TIDAK boleh query, hapus baris markSearch() di bawah ini. */
+			markSearch();
 			document.forms.dlog.submit();
 			doDisable(document.forms.dlog, "button");
 
 		}
 		
 		function doOrder(field, type){
+			syncTimestampFilter();
+			markSearch();
 			document.forms.dlog.dlog_fieldOrder.value=field;
 			document.forms.dlog.dlog_orderType.value=type;
 			document.forms.dlog.action.value="list";
@@ -159,6 +188,7 @@
 <input type="hidden" name="clear" value="">
 <input type="hidden" name="count" value="">
 <input type="hidden" name="id" value="">
+<input type="hidden" name="doSearch" value="">
 <input type="hidden" name="dlog_fieldOrder" value="<bean:write name="dlog_fieldOrder"/>">
 <input type="hidden" name="dlog_orderType" 	value="<bean:write name="dlog_orderType"/>">
 <input type="hidden" name="labelFilter" value="<logic:notEmpty name="dlogMd_label_filter"><bean:write name="dlogMd_label_filter" /></logic:notEmpty>">
@@ -172,12 +202,15 @@
 			<td class="titleHeader"><bean:message key="project.dlog.key"/> - LIST</td>
 	  	</tr>
 			<tr><td><html:errors /></td></tr>
+	  	<logic:notEmpty name="paging">
 	  	<tr>
 			<td><%@ include file="../common/paging.jsp"%></td> 
 		</tr> 
+		</logic:notEmpty>
 	</table> 
 
- 	<table width="100%" border="0" cellspacing="1" cellpadding="1">
+ 	<logic:notEmpty name="dlogList">
+<table width="100%" border="0" cellspacing="1" cellpadding="1">
  		<tr>
 			<td colspan="7">
 				<div id="message"></div>
@@ -239,8 +272,7 @@
 			</td>
 			<td width="5%" class="titleField">&nbsp;</td>
 		</tr>	
-		<logic:notEmpty name="dlogList">
-			<logic:iterate id="element" name="dlogList" scope="request" indexId="index">
+		<logic:iterate id="element" name="dlogList" scope="request" indexId="index">
 			<% 
 				sequence++;
 				String bgcolor = "FFFFFF";
@@ -267,13 +299,29 @@
 					 <td align="center" class="celBorder" id="tdId"><a href="#"><img src="./images/detail.gif" border="0" onclick="doDetail('<bean:write name="element" property="id"/>');" alt="see detail"></a></td>
 				</tr>
 			</logic:iterate>
-		</logic:notEmpty>
 	</table>
 	<table width="100%" border="0" align="center" cellpadding="2" cellspacing="4"> 
 	  	<tr>
 			<td><%@ include file="../common/paging.jsp"%></td> 
 		</tr> 
-	</table>  
+	</table>
+</logic:notEmpty>  
+<logic:empty name="dlogList">
+	<table width="100%" border="0" align="center" cellpadding="2" cellspacing="4">
+		<tr>
+			<td>
+			<%-- CR#2: bedakan pesan "belum search" vs "sudah search tapi hasilnya kosong",
+			     supaya pencarian valid dengan 0 hasil tidak terlihat seperti belum di-search --%>
+			<logic:equal name="searched" value="true">
+				Tidak ada log yang ditemukan untuk filter yang dipilih.
+			</logic:equal>
+			<logic:notEqual name="searched" value="true">
+				Silakan tentukan tanggal dan filter yang diinginkan, lalu klik <b>Search</b>.
+			</logic:notEqual>
+			</td>
+		</tr>
+	</table>
+</logic:empty>
    	<table width="100%" border="0" align="center" cellpadding="2" cellspacing="4">
 	<br>	
 		<tr> 
@@ -302,64 +350,40 @@
 			</td>
 		</tr>
 		<tr>
-			<td width="20%"><b><bean:message key="dlog.timestamp.key"/></b></td>
+			<td width="20%"><b><bean:message key="dlog.timestamp.key"/></b> <span style="font-weight:normal;">(1 hari)</span></td>
 			<td width="1%">:</td>
 			<td width="79%">
-				<input type="text" name="dlog_timestamp_start_filter" value="<logic:notEmpty name="dlog_timestamp_start_filter"><bean:write name="dlog_timestamp_start_filter" format="dd/MM/yyyy" /></logic:notEmpty>" size="12" maxlength="10">
+				<input type="text" name="dlog_timestamp_start_filter" value="<logic:notEmpty name="dlog_timestamp_start_filter"><bean:write name="dlog_timestamp_start_filter" format="dd/MM/yyyy" /></logic:notEmpty>" size="12" maxlength="10" readonly="readonly">
 				<a href="javascript:cal_timestamp_start_filter.popup();"><img alt="cal" src="./images/cal.gif" width="16" height="16" border="0"></a>
 				<script type="text/javascript" language="javascript">
 					var cal_timestamp_start_filter = new calendar1(document.forms['dlog'].elements['dlog_timestamp_start_filter']);
 				</script>
-				<select name="dlog_timestamp_hour_start_filter" style="width:70">
-					<logic:notEmpty name="hourList">
-					<logic:iterate id="element_hour" name="hourList" indexId="hourIndex">
-						<option value="${hourIndex}"
-					<logic:equal name="dlog_timestamp_hour_start_filter"  value="${hourIndex}">
-						selected
-					</logic:equal>
-						><bean:write name="element_hour" /></option>
-					</logic:iterate>
-					</logic:notEmpty>
-				</select>
-				<select name="dlog_timestamp_minute_start_filter" style="width:70">
-					<logic:notEmpty name="minuteList">
-					<logic:iterate id="element_minute" name="minuteList" indexId="minuteIndex">
-						<option value="${minuteIndex}"
-					<logic:equal name="dlog_timestamp_minute_start_filter" value="${minuteIndex}">
-						selected
-					</logic:equal>
-						><bean:write name="element_minute"/></option>
-					</logic:iterate>
-					</logic:notEmpty>
-				</select>
-				&nbsp;To&nbsp;
 
-				<input type="text" name="dlog_timestamp_end_filter" value="<logic:notEmpty name="dlog_timestamp_end_filter"><bean:write name="dlog_timestamp_end_filter" format="dd/MM/yyyy" /></logic:notEmpty>" size="12" maxlength="10">
-				<a href="javascript:cal_dlog_timestamp_end_filter.popup();"><img alt="cal" src="./images/cal.gif" width="16" height="16" border="0"></a>
-				<script type="text/javascript" language="javascript">
-					var cal_dlog_timestamp_end_filter = new calendar1(document.forms['dlog'].elements['dlog_timestamp_end_filter']);
-				</script>
-				<select name="dlog_timestamp_hour_end_filter" style="width:70">
-					<logic:notEmpty name="hourList">
-					<logic:iterate id="element_hour" name="hourList" indexId="hourIndex">
-						<option value="${hourIndex}"
-					<logic:equal name="dlog_timestamp_hour_end_filter" value="${hourIndex}">
-						selected
-					</logic:equal>
-						><bean:write name="element_hour" /></option>
-					</logic:iterate>
-					</logic:notEmpty>
+				<!-- End date selalu ikut start date (tanggal sama, tidak ada picker terpisah) -->
+				<input type="hidden" name="dlog_timestamp_end_filter" value="<logic:notEmpty name="dlog_timestamp_start_filter"><bean:write name="dlog_timestamp_start_filter" format="dd/MM/yyyy" /></logic:notEmpty>">
+
+				&nbsp;&nbsp;<b>Start:</b>
+				<select name="dlog_timestamp_hour_start_filter" style="width:55">
+				<logic:iterate id="hourStartItem" name="hourList">
+					<option value="<bean:write name="hourStartItem"/>"<logic:equal name="dlog_timestamp_hour_start_filter" value="${hourStartItem}"> selected</logic:equal>><bean:write name="hourStartItem"/></option>
+				</logic:iterate>
+				</select> :
+				<select name="dlog_timestamp_minute_start_filter" style="width:55">
+				<logic:iterate id="minuteStartItem" name="minuteList">
+					<option value="<bean:write name="minuteStartItem"/>"<logic:equal name="dlog_timestamp_minute_start_filter" value="${minuteStartItem}"> selected</logic:equal>><bean:write name="minuteStartItem"/></option>
+				</logic:iterate>
 				</select>
-				<select name="dlog_timestamp_minute_end_filter" style="width:70">
-					<logic:notEmpty name="minuteList">
-					<logic:iterate id="element_minute" name="minuteList" indexId="minuteIndex">
-						<option value="${minuteIndex}"
-					<logic:equal name="dlog_timestamp_minute_end_filter" value="${minuteIndex}">
-						selected
-					</logic:equal>
-						><bean:write name="element_minute" /></option>
-					</logic:iterate>
-					</logic:notEmpty>
+
+				&nbsp;&nbsp;<b>End:</b>
+				<select name="dlog_timestamp_hour_end_filter" style="width:55">
+				<logic:iterate id="hourEndItem" name="hourList">
+					<option value="<bean:write name="hourEndItem"/>"<logic:equal name="dlog_timestamp_hour_end_filter" value="${hourEndItem}"> selected</logic:equal>><bean:write name="hourEndItem"/></option>
+				</logic:iterate>
+				</select> :
+				<select name="dlog_timestamp_minute_end_filter" style="width:55">
+				<logic:iterate id="minuteEndItem" name="minuteList">
+					<option value="<bean:write name="minuteEndItem"/>"<logic:equal name="dlog_timestamp_minute_end_filter" value="${minuteEndItem}"> selected</logic:equal>><bean:write name="minuteEndItem"/></option>
+				</logic:iterate>
 				</select>
 			</td>
 		</tr>
@@ -367,7 +391,13 @@
 			<td width="20%"><b><bean:message key="dlog.source.key"/></b></td>
 			<td width="1%">:</td>
 			<td width="79%">
-			<label> 
+			<select name="dlog_source_sma" style="width:100">
+				<option value="equal"<logic:equal name="dlog_source_sma" value="equal"> selected</logic:equal>>Equal</option>
+				<option value="not_equal"<logic:equal name="dlog_source_sma" value="not_equal"> selected</logic:equal>>Not Equal</option>
+				<option value="like"<logic:equal name="dlog_source_sma" value="like"> selected</logic:equal>>Like</option>
+				<option value="not_like"<logic:equal name="dlog_source_sma" value="not_like"> selected</logic:equal>>Not Like</option>
+			</select>
+			<label>
 				<input type="text" name="dlog_source_filter" value="<logic:notEmpty name="dlog_source_filter"><bean:write name="dlog_source_filter"/></logic:notEmpty>">
 			</label>
 			</td>
@@ -376,7 +406,13 @@
 			<td width="20%"><b><bean:message key="dlog.code.key"/></b></td>
 			<td width="1%">:</td>
 			<td width="79%">
-			<label> 
+			<select name="dlog_code_sma" style="width:100">
+				<option value="equal"<logic:equal name="dlog_code_sma" value="equal"> selected</logic:equal>>Equal</option>
+				<option value="not_equal"<logic:equal name="dlog_code_sma" value="not_equal"> selected</logic:equal>>Not Equal</option>
+				<option value="like"<logic:equal name="dlog_code_sma" value="like"> selected</logic:equal>>Like</option>
+				<option value="not_like"<logic:equal name="dlog_code_sma" value="not_like"> selected</logic:equal>>Not Like</option>
+			</select>
+			<label>
 				<input type="text" name="dlog_code_filter" value="<logic:notEmpty name="dlog_code_filter"><bean:write name="dlog_code_filter"/></logic:notEmpty>">
 			</label>
 			</td>
@@ -399,31 +435,11 @@
 				</logic:notEmpty>
 				</select>
 				
-				<select name="dlogMd_sma" style="width:42">
-					<option value="="
-			    <logic:notEmpty name="dlogMd_sma">
-				<logic:equal name="dlogMd_sma" value="=">
-					selected
-				</logic:equal>
-				</logic:notEmpty>
-					>=
-					</option>
-				<option value="!="
-				<logic:notEmpty name="dlogMd_sma">
-				<logic:equal name="dlogMd_sma" value="!=">
-					selected
-				</logic:equal>
-				</logic:notEmpty>
-					>!=
-					</option>
-					<option value="=="
-			    <logic:notEmpty name="dlogMd_sma">
-				<logic:equal name="dlogMd_sma" value="==">
-					selected
-				</logic:equal>
-				</logic:notEmpty>
-					>==
-					</option>
+				<select name="dlogMd_sma" style="width:100">
+					<option value="equal"<logic:equal name="dlogMd_sma" value="equal"> selected</logic:equal>>Equal</option>
+					<option value="not_equal"<logic:equal name="dlogMd_sma" value="not_equal"> selected</logic:equal>>Not Equal</option>
+					<option value="like"<logic:equal name="dlogMd_sma" value="like"> selected</logic:equal>>Like</option>
+					<option value="not_like"<logic:equal name="dlogMd_sma" value="not_like"> selected</logic:equal>>Not Like</option>
 				</select>
 				
 			</td>
@@ -456,4 +472,4 @@
 	<script type="text/javascript" language="javascript">loadPageInit();</script>
 </logic:notEmpty> 
 <!-- END CONTENT -->  
-<%@ include file="../common/footer.jsp"%>
+<%@ include file="../common/footer.jsp"%>

+ 234 - 98
MWUI/src/id/co/hanoman/log/action/DlogAction.java

@@ -54,6 +54,24 @@ import id.co.hanoman.shared.Util;
  * @author Hanoman Interaktif<BR>
  *         (C) <a href="http://www.hanoman.co.id">Hanoman Interaktif.</a>
  * @version 1.0
+ *
+ * CR MWUI (terbaru):
+ * 1. Filter source/code/custom field (v_XX) tidak lagi selalu "like" - sekarang mendukung
+ *    equal / not_equal / like / not_like, default "equal".
+ * 2. Initial load (belum ada aksi search/page/order/reset dari user) tidak query ke tabel DLOG,
+ *    hanya menampilkan form filter. Ditandai lewat parameter hidden "doSearch".
+ * 3. Paging tidak lagi menghitung total record (getTotal()/COUNT). Diganti dengan strategi
+ *    "ambil count+1 baris" untuk mendeteksi apakah ada halaman berikutnya, sehingga paging
+ *    hanya menampilkan tombol Next (dan Prev/First). Karena ini tidak lagi memakai
+ *    Pager.generatePagingNew()/generatePagingItemNew() (yang berbasis total record), HTML
+ *    paging dibangun langsung di sini.
+ * 4. Filter waktu dibatasi maksimal 1 hari: user hanya memilih 1 tanggal (end date server-side
+ *    selalu dipaksa = start date), tapi start time & end time (jam:menit) dalam tanggal itu
+ *    tetap bisa diatur user.
+ * 5. Response header LOG MESSAGE - LIST diset no-cache/no-store supaya browser tidak
+ *    menampilkan ulang hasil search lama dari cache saat halaman dibuka lagi.
+ * 6. Attribute request "searched" ditambahkan supaya dlog_list.jsp bisa membedakan pesan
+ *    "belum melakukan search" vs "sudah search tapi hasilnya memang kosong".
  */
 
 public class DlogAction extends Action {
@@ -61,6 +79,12 @@ public class DlogAction extends Action {
 	protected Log log = LogFactory.getLog(this.getClass());
 	private SimpleDateFormat formatSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
 
+	// nilai default operator filter (CR#1)
+	private static final String SMA_EQUAL = "equal";
+	private static final String SMA_NOT_EQUAL = "not_equal";
+	private static final String SMA_LIKE = "like";
+	private static final String SMA_NOT_LIKE = "not_like";
+
 	/**
 	 * Method execute mapping forward
 	 *<p>
@@ -119,7 +143,9 @@ public class DlogAction extends Action {
 	public void clearSession(HttpServletRequest request) throws Exception {
 		request.getSession().removeAttribute("dlog_type_filter");
 		request.getSession().removeAttribute("dlog_source_filter");
+		request.getSession().removeAttribute("dlog_source_sma");
 		request.getSession().removeAttribute("dlog_code_filter");
+		request.getSession().removeAttribute("dlog_code_sma");
 		// request.getSession().removeAttribute("dlog_timestamp_start_filter");
 		// set default to Current Date
 		request.getSession().setAttribute("dlog_timestamp_start_filter", Constant.SDFDS.format(new Date()));
@@ -130,6 +156,7 @@ public class DlogAction extends Action {
 		request.getSession().removeAttribute("dlog_timestamp_minute_end_filter");
 		request.getSession().removeAttribute("dlogMd_sma");
 		request.getSession().removeAttribute("dlog_value_filter");
+		request.getSession().removeAttribute("dlogMd_label_filter");
 		request.getSession().removeAttribute("dlog_fieldOrder");
 		request.getSession().removeAttribute("dlog_orderType");
 		request.getSession().removeAttribute("start");
@@ -153,12 +180,25 @@ public class DlogAction extends Action {
 		if (request.getParameter(Constant.CLEAR) != null && Constant.TRUE.equalsIgnoreCase(request.getParameter(Constant.CLEAR)))
 			clearSession(request);
 
+		// ==== Cegah browser menampilkan ulang halaman hasil search lewat cache / back-forward
+		//      cache (bfcache) saat user membuka lagi halaman LOG MESSAGE, supaya initial load
+		//      selalu benar-benar hit server dan dievaluasi ulang lewat flag doSearch. ====
+		response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
+		response.setHeader("Expires", "0");
+
 		ServletContext ctx = getServlet().getServletContext();
 		PersistanceServiceInterface psi = ServiceUtil.getDaoClient(ctx);
 
 		request.setAttribute("hourList", Constant.HOUR_LIST);
 		request.setAttribute("minuteList", Constant.MINUTE_LIST);
 
+		// CR#2: apakah request ini benar-benar aksi user (search/page/order/reset)
+		// atau cuma buka halaman pertama kali. Kalau bukan aksi user, jangan query ke DLOG.
+		String param_doSearch = request.getParameter("doSearch");
+		boolean isSearch = Constant.TRUE.equalsIgnoreCase(param_doSearch);
+		// dipakai di JSP untuk membedakan pesan "belum search" vs "sudah search tapi hasil kosong"
+		request.setAttribute("searched", isSearch ? "true" : "false");
+
 		// this for orderBy field ASC/DESC
 		String dlog_fieldOrder = request.getParameter("dlog_fieldOrder");
 		String dlog_orderType = request.getParameter("dlog_orderType");
@@ -166,7 +206,9 @@ public class DlogAction extends Action {
 		// this for filter
 		String param_dlog_type_filter = request.getParameter("dlog_type_filter");
 		String param_dlog_source_filter = request.getParameter("dlog_source_filter");
+		String param_dlog_source_sma = request.getParameter("dlog_source_sma");
 		String param_dlog_code_filter = request.getParameter("dlog_code_filter");
+		String param_dlog_code_sma = request.getParameter("dlog_code_sma");
 		String param_dlogMd_label_filter = request.getParameter("dlogMd_label_filter");
 		String param_dlog_value_filter = request.getParameter("dlog_value_filter");
 		String param_dlog_timestamp_start_filter = request.getParameter("dlog_timestamp_start_filter");
@@ -189,8 +231,16 @@ public class DlogAction extends Action {
 			param_dlog_type_filter = (String) request.getSession().getAttribute("dlog_type_filter");
 		if (param_dlog_source_filter == null)
 			param_dlog_source_filter = (String) request.getSession().getAttribute("dlog_source_filter");
+		if (param_dlog_source_sma == null)
+			param_dlog_source_sma = (String) request.getSession().getAttribute("dlog_source_sma");
+		if (Util.isNullOrEmpty(param_dlog_source_sma))
+			param_dlog_source_sma = SMA_EQUAL; // default CR#1
 		if (param_dlog_code_filter == null)
 			param_dlog_code_filter = (String) request.getSession().getAttribute("dlog_code_filter");
+		if (param_dlog_code_sma == null)
+			param_dlog_code_sma = (String) request.getSession().getAttribute("dlog_code_sma");
+		if (Util.isNullOrEmpty(param_dlog_code_sma))
+			param_dlog_code_sma = SMA_EQUAL; // default CR#1
 		if (param_dlogMd_label_filter == null)
 			param_dlogMd_label_filter = (String) request.getSession().getAttribute("dlogMd_label_filter");
 		if (param_dlog_value_filter == null)
@@ -220,6 +270,8 @@ public class DlogAction extends Action {
 			param_dlog_timestamp_minute_end_filter = (String) request.getSession().getAttribute("dlog_timestamp_minute_end_filter");
 		if (param_dlog_sma_filter == null)
 			param_dlog_sma_filter = (String) request.getSession().getAttribute("dlogMd_sma");
+		if (Util.isNullOrEmpty(param_dlog_sma_filter))
+			param_dlog_sma_filter = SMA_EQUAL; // default CR#1
 
 		// default untuk order by timestamp desc
 		if (Util.isNullOrEmpty(dlog_fieldOrder))
@@ -247,93 +299,155 @@ public class DlogAction extends Action {
 		}
 		request.getSession().setAttribute("dlog_type_filter", param_dlog_type_filter);
 
-		// filter for timestamp
+		// ==== FILTER WAKTU: DIBATASI MAKSIMAL 1 HARI ====
+		// User hanya memilih SATU tanggal (dlog_timestamp_start_filter); end date selalu
+		// dipaksa sama dengan start date di server (parameter end date dari client diabaikan),
+		// jadi range tetap maksimal 1 hari walaupun ada yang mengoplos parameter lewat URL.
+		// Start time & end time (jam:menit) dalam tanggal itu tetap bisa diatur user.
+		if (Util.isNullOrEmpty(param_dlog_timestamp_hour_start_filter))
+			param_dlog_timestamp_hour_start_filter = "0";
+		if (Util.isNullOrEmpty(param_dlog_timestamp_minute_start_filter))
+			param_dlog_timestamp_minute_start_filter = "0";
+		if (Util.isNullOrEmpty(param_dlog_timestamp_hour_end_filter))
+			param_dlog_timestamp_hour_end_filter = "23";
+		if (Util.isNullOrEmpty(param_dlog_timestamp_minute_end_filter))
+			param_dlog_timestamp_minute_end_filter = "59";
+		// end date selalu ikut start date, apapun yang dikirim client
+		param_dlog_timestamp_end_filter = param_dlog_timestamp_start_filter;
+
 		Calendar param_dlog_timestamp_start_filter_cal = Calendar.getInstance();
+		Calendar param_dlog_timestamp_end_filter_cal = Calendar.getInstance();
 		if (!Util.isNullOrEmpty(param_dlog_timestamp_start_filter)) {
-			if (param_dlog_timestamp_start_filter.length() > 0 && !Util.isNullOrEmpty(param_dlog_timestamp_hour_start_filter) && !Util.isNullOrEmpty(param_dlog_timestamp_minute_start_filter)) {
-				try {
-					param_dlog_timestamp_start_filter_cal.setTime(new SimpleDateFormat(Constant.DATE_SHORT).parse(param_dlog_timestamp_start_filter));
-					param_dlog_timestamp_start_filter_cal.set(Calendar.HOUR, Integer.parseInt(param_dlog_timestamp_hour_start_filter));
-					param_dlog_timestamp_start_filter_cal.set(Calendar.MINUTE, Integer.parseInt(param_dlog_timestamp_minute_start_filter));
-					// log.info("start getTime() :: "
-					// +param_dlog_timestamp_start_filter_cal.getTime());
-					filter = new NameValue();
-					filter.setName("timestampStart");
-					filter.setValue(param_dlog_timestamp_start_filter_cal.getTime());
-					filterList.add(filter);
-					dlog_filterSb.append(" AND dlog.reqtimestamp >= :timestampStart ");
-				} catch (Exception e) {
+			try {
+				param_dlog_timestamp_start_filter_cal.setTime(new SimpleDateFormat(Constant.DATE_SHORT).parse(param_dlog_timestamp_start_filter));
+				param_dlog_timestamp_start_filter_cal.set(Calendar.HOUR_OF_DAY, Integer.parseInt(param_dlog_timestamp_hour_start_filter));
+				param_dlog_timestamp_start_filter_cal.set(Calendar.MINUTE, Integer.parseInt(param_dlog_timestamp_minute_start_filter));
+				param_dlog_timestamp_start_filter_cal.set(Calendar.SECOND, 0);
+				param_dlog_timestamp_start_filter_cal.set(Calendar.MILLISECOND, 0);
+
+				// end pakai tanggal yang sama (bukan hasil parse ulang param_dlog_timestamp_end_filter,
+				// supaya walaupun ada yang usil kirim tanggal lain di parameter itu, tetap diabaikan)
+				param_dlog_timestamp_end_filter_cal.setTime(param_dlog_timestamp_start_filter_cal.getTime());
+				param_dlog_timestamp_end_filter_cal.set(Calendar.HOUR_OF_DAY, Integer.parseInt(param_dlog_timestamp_hour_end_filter));
+				param_dlog_timestamp_end_filter_cal.set(Calendar.MINUTE, Integer.parseInt(param_dlog_timestamp_minute_end_filter));
+				param_dlog_timestamp_end_filter_cal.set(Calendar.SECOND, 59);
+				param_dlog_timestamp_end_filter_cal.set(Calendar.MILLISECOND, 999);
+
+				// jaga-jaga kalau end time-of-day < start time-of-day (misal user pilih start 20:00, end 08:00) -> tukar
+				if (param_dlog_timestamp_end_filter_cal.before(param_dlog_timestamp_start_filter_cal)) {
+					Calendar tmp = (Calendar) param_dlog_timestamp_start_filter_cal.clone();
+					param_dlog_timestamp_start_filter_cal.set(Calendar.HOUR_OF_DAY, param_dlog_timestamp_end_filter_cal.get(Calendar.HOUR_OF_DAY));
+					param_dlog_timestamp_start_filter_cal.set(Calendar.MINUTE, param_dlog_timestamp_end_filter_cal.get(Calendar.MINUTE));
+					param_dlog_timestamp_start_filter_cal.set(Calendar.SECOND, 0);
+					param_dlog_timestamp_start_filter_cal.set(Calendar.MILLISECOND, 0);
+					param_dlog_timestamp_end_filter_cal.set(Calendar.HOUR_OF_DAY, tmp.get(Calendar.HOUR_OF_DAY));
+					param_dlog_timestamp_end_filter_cal.set(Calendar.MINUTE, tmp.get(Calendar.MINUTE));
+					param_dlog_timestamp_end_filter_cal.set(Calendar.SECOND, 59);
+					param_dlog_timestamp_end_filter_cal.set(Calendar.MILLISECOND, 999);
+					String tmpH = param_dlog_timestamp_hour_start_filter, tmpM = param_dlog_timestamp_minute_start_filter;
+					param_dlog_timestamp_hour_start_filter = param_dlog_timestamp_hour_end_filter;
+					param_dlog_timestamp_minute_start_filter = param_dlog_timestamp_minute_end_filter;
+					param_dlog_timestamp_hour_end_filter = tmpH;
+					param_dlog_timestamp_minute_end_filter = tmpM;
 				}
+
+				filter = new NameValue();
+				filter.setName("timestampStart");
+				filter.setValue(param_dlog_timestamp_start_filter_cal.getTime());
+				filterList.add(filter);
+				dlog_filterSb.append(" AND dlog.reqtimestamp >= :timestampStart ");
+
+				filter = new NameValue();
+				filter.setName("timestampEnd");
+				filter.setValue(param_dlog_timestamp_end_filter_cal.getTime());
+				filterList.add(filter);
+				dlog_filterSb.append(" AND dlog.reqtimestamp <= :timestampEnd ");
+
+				log.info("FILTER TANGGAL (1 hari) :: " + formatSdf.format(param_dlog_timestamp_start_filter_cal.getTime()) + " s/d " + formatSdf.format(param_dlog_timestamp_end_filter_cal.getTime()));
+			} catch (Exception e) {
+				log.error("Gagal parse tanggal/jam filter [" + param_dlog_timestamp_start_filter + " "
+						+ param_dlog_timestamp_hour_start_filter + ":" + param_dlog_timestamp_minute_start_filter + " - "
+						+ param_dlog_timestamp_hour_end_filter + ":" + param_dlog_timestamp_minute_end_filter + "]", e);
 			}
 		}
+
 		request.getSession().setAttribute("dlog_timestamp_start_filter", param_dlog_timestamp_start_filter);
 		request.getSession().setAttribute("dlog_timestamp_hour_start_filter", param_dlog_timestamp_hour_start_filter);
 		request.getSession().setAttribute("dlog_timestamp_minute_start_filter", param_dlog_timestamp_minute_start_filter);
-
-		Calendar param_dlog_timestamp_end_filter_cal = Calendar.getInstance();
-		if (!Util.isNullOrEmpty(param_dlog_timestamp_end_filter)) {
-			if (param_dlog_timestamp_end_filter.length() > 0 && !Util.isNullOrEmpty(param_dlog_timestamp_hour_end_filter) && !Util.isNullOrEmpty(param_dlog_timestamp_minute_end_filter)) {
-				try {
-					param_dlog_timestamp_end_filter_cal.setTime(new SimpleDateFormat(Constant.DATE_SHORT).parse(param_dlog_timestamp_end_filter));
-					param_dlog_timestamp_end_filter_cal.set(Calendar.HOUR, Integer.parseInt(param_dlog_timestamp_hour_end_filter));
-					param_dlog_timestamp_end_filter_cal.set(Calendar.MINUTE, Integer.parseInt(param_dlog_timestamp_minute_end_filter));
-					// log.info("end getTime() :: "
-					// +param_dlog_timestamp_end_filter_cal.getTime());
-					filter = new NameValue();
-					filter.setName("timestampEnd");
-					filter.setValue(param_dlog_timestamp_end_filter_cal.getTime());
-					filterList.add(filter);
-					dlog_filterSb.append(" AND dlog.reqtimestamp <= :timestampEnd ");
-				} catch (Exception e) {
-				}
-			}
-		}
 		request.getSession().setAttribute("dlog_timestamp_end_filter", param_dlog_timestamp_end_filter);
 		request.getSession().setAttribute("dlog_timestamp_hour_end_filter", param_dlog_timestamp_hour_end_filter);
 		request.getSession().setAttribute("dlog_timestamp_minute_end_filter", param_dlog_timestamp_minute_end_filter);
 
+		// ==== CR#1: SOURCE - equal / not_equal / like / not_like (default equal) ====
 		if (!Util.isNullOrEmpty(param_dlog_source_filter)) {
 			if (param_dlog_source_filter.length() > 0) {
 				filter = new NameValue();
 				filter.setName("source");
-				filter.setValue("%" + param_dlog_source_filter.trim() + "%");
+				if (SMA_LIKE.equals(param_dlog_source_sma)) {
+					filter.setValue("%" + param_dlog_source_filter.trim() + "%");
+					dlog_filterSb.append(" AND dlog.source like :source ");
+				} else if (SMA_NOT_LIKE.equals(param_dlog_source_sma)) {
+					filter.setValue("%" + param_dlog_source_filter.trim() + "%");
+					dlog_filterSb.append(" AND dlog.source not like :source ");
+				} else if (SMA_NOT_EQUAL.equals(param_dlog_source_sma)) {
+					filter.setValue(param_dlog_source_filter.trim());
+					dlog_filterSb.append(" AND dlog.source != :source ");
+				} else {
+					filter.setValue(param_dlog_source_filter.trim());
+					dlog_filterSb.append(" AND dlog.source = :source ");
+				}
 				filterList.add(filter);
-				dlog_filterSb.append(" AND dlog.source like :source ");
 			}
 		}
 		request.getSession().setAttribute("dlog_source_filter", param_dlog_source_filter);
+		request.getSession().setAttribute("dlog_source_sma", param_dlog_source_sma);
 
+		// ==== CR#1: CODE - equal / not_equal / like / not_like (default equal) ====
 		if (!Util.isNullOrEmpty(param_dlog_code_filter)) {
 			if (param_dlog_code_filter.length() > 0) {
 				filter = new NameValue();
 				filter.setName("code");
-				filter.setValue("%" + param_dlog_code_filter.trim() + "%");
+				if (SMA_LIKE.equals(param_dlog_code_sma)) {
+					filter.setValue("%" + param_dlog_code_filter.trim() + "%");
+					dlog_filterSb.append(" AND dlog.code like :code ");
+				} else if (SMA_NOT_LIKE.equals(param_dlog_code_sma)) {
+					filter.setValue("%" + param_dlog_code_filter.trim() + "%");
+					dlog_filterSb.append(" AND dlog.code not like :code ");
+				} else if (SMA_NOT_EQUAL.equals(param_dlog_code_sma)) {
+					filter.setValue(param_dlog_code_filter.trim());
+					dlog_filterSb.append(" AND dlog.code != :code ");
+				} else {
+					filter.setValue(param_dlog_code_filter.trim());
+					dlog_filterSb.append(" AND dlog.code = :code ");
+				}
 				filterList.add(filter);
-				dlog_filterSb.append(" AND dlog.code like :code ");
 			}
 		}
 		request.getSession().setAttribute("dlog_code_filter", param_dlog_code_filter);
+		request.getSession().setAttribute("dlog_code_sma", param_dlog_code_sma);
 
+		// ==== CR#1: CUSTOM FIELD (v_XX via dlogMd_label_filter) - equal / not_equal / like / not_like (default equal) ====
 		if (!Util.isNullOrEmpty(param_dlog_value_filter)) {
 			if (!Util.isNullOrEmpty(param_dlogMd_label_filter)) {
-				if (!Util.isNullOrEmpty(param_dlog_sma_filter)) {
-					if (param_dlog_value_filter.length() > 0) {
-						// edited by ismawan
-						if (param_dlog_sma_filter.equals("=")) {
-							filter = new NameValue();
-							filter.setName("id");
-							filter.setValue("%" + param_dlog_value_filter.trim() + "%");
-							filterList.add(filter);
-							dlog_filterSb.append(" AND dlog." + param_dlogMd_label_filter + " like :id ");
-							// log.info("dlog_filterSb ["+dlog_filterSb.toString()+"]"); //cek, udah nangkap parameternya lum
-						} else if (param_dlog_sma_filter.equals("!=")) {
-							filter = new NameValue();
-							filter.setName("id");
-							filter.setValue("%" + param_dlog_value_filter.trim() + "%");
-							filterList.add(filter);
-							dlog_filterSb.append(" AND dlog." + param_dlogMd_label_filter + " not like :id ");
-						}
+				if (param_dlog_value_filter.length() > 0) {
+					filter = new NameValue();
+					filter.setName("id");
+					String col = "dlog." + param_dlogMd_label_filter;
+					if (SMA_LIKE.equals(param_dlog_sma_filter)) {
+						filter.setValue("%" + param_dlog_value_filter.trim() + "%");
+						dlog_filterSb.append(" AND " + col + " like :id ");
+					} else if (SMA_NOT_LIKE.equals(param_dlog_sma_filter)) {
+						filter.setValue("%" + param_dlog_value_filter.trim() + "%");
+						dlog_filterSb.append(" AND " + col + " not like :id ");
+					} else if (SMA_NOT_EQUAL.equals(param_dlog_sma_filter)) {
+						filter.setValue(param_dlog_value_filter.trim());
+						dlog_filterSb.append(" AND " + col + " != :id ");
+					} else {
+						filter.setValue(param_dlog_value_filter.trim());
+						dlog_filterSb.append(" AND " + col + " = :id ");
 					}
+					filterList.add(filter);
+					// log.info("dlog_filterSb ["+dlog_filterSb.toString()+"]"); //cek, udah nangkap parameternya lum
 				}
 			}
 		}
@@ -366,53 +480,75 @@ public class DlogAction extends Action {
 			} catch (Exception e) {
 			}
 
-			NameValue[] filterArray = new NameValue[filterList.size()];
-			for (int i = 0; i < filterList.size(); i++) {
-				filterArray[i] = filterList.get(i);
+			// dlogMdList (dropdown Type) dan typeMap tetap perlu dimuat di initial load,
+			// karena ini query ke DlogMd (metadata), BUKAN ke tabel DLOG.
+			id.co.hanoman.service.List typeList = null;
+			if (typeMap == null) {
+				typeMap = getTypeList(psi, typeList);
 			}
+			request.setAttribute("dlogMdList", typeMap.keySet());
 
-			log.info("WHERE CLAUSE = " + dlog_filterSb.toString());
-			System.out.println("------------ DLOGLIST ---------------  FROM DlogList dlog WHERE 1=1 " + dlog_filterSb +" order "+ order+" ??? "+filterArray);
-			id.co.hanoman.service.List list = psi.query(Constant.MWLOG_DS, "FROM DlogList dlog WHERE 1=1 " + dlog_filterSb + (Util.isNullOrEmpty(order) ? "" : " ORDER BY " + order), filterArray, start, count);
+			if (isSearch) {
+				// ==== CR#2: hanya query ke tabel DLOG kalau ini benar-benar aksi user ====
 
+				NameValue[] filterArray = new NameValue[filterList.size()];
+				for (int i = 0; i < filterList.size(); i++) {
+					filterArray[i] = filterList.get(i);
+				}
 
-			Object[] items = list.getItems();
-			String paging = Pager.generatePagingNew(start, list.getTotal(), Constant.PAGE_SIZE_LOG);
-			String pagingItem = Pager.generatePagingItemNew(start, list.getTotal(), Constant.PAGE_SIZE_LOG);
+				log.info("WHERE CLAUSE = " + dlog_filterSb.toString());
+				log.info("------------ DLOGLIST ---------------  FROM DlogList dlog WHERE 1=1 " + dlog_filterSb +" order "+ order+" ??? "+filterArray);
 
-			id.co.hanoman.service.List typeList = null;
-			if (typeMap == null) {
-				typeMap = getTypeList(psi, typeList);
-			}
+				// ==== CR#3: ambil count+1 baris untuk deteksi next page, tanpa hitung total ====
+				id.co.hanoman.service.List list = psi.query(Constant.MWLOG_DS, "FROM DlogList dlog WHERE 1=1 " + dlog_filterSb + (Util.isNullOrEmpty(order) ? "" : " ORDER BY " + order), filterArray, start, count + 1);
 
-			// String type = request.getParameter("dlog_type_filter");
-			// if(!Util.isNullOrEmpty(type)) {
-			// List<Map> labelList = new LinkedList();
-			// Iterator iterator = typeMap.keySet().iterator();
-			// while(iterator.hasNext()) {
-			// Object o = iterator.next();
-			// if(o instanceof String) {
-			// String typeStr = (String) o;
-			// if(typeStr.equalsIgnoreCase(type)) {
-			// labelList = (List<Map>)typeMap.get(typeStr);
-			// }
-			// }
-			// }
-			// log.info("LABEL_LIST::"+labelList);
-			// request.setAttribute("labelList", labelList);
-			// }
+				Object[] rawItems = list.getItems();
+				boolean hasNext = rawItems != null && rawItems.length > count;
+				int showCount = rawItems == null ? 0 : (hasNext ? count : rawItems.length);
+				Object[] items = new Object[showCount];
+				if (rawItems != null && showCount > 0) {
+					System.arraycopy(rawItems, 0, items, 0, showCount);
+				}
 
-			request.setAttribute("dlogMdList", typeMap.keySet());
-			request.setAttribute("paging", paging);
-			request.setAttribute("pagingItem", pagingItem);
-			request.setAttribute("dlogList", items);
-			request.setAttribute("dlog_start", new Integer(start));
-			for(int i = 0; i < items.length;i++) {
-				DlogList dlogList = (DlogList)items[i];
-				System.out.println("-dlogList.getCode()- "+dlogList.getCode());
-				System.out.println("-dlogList.getSource()- "+dlogList.getSource());
-			}
-			System.out.println("------------ DLOGLIST --------------- "+list.getTotal()+" FROM DlogList dlog WHERE 1=1 " + dlog_filterSb +" order "+ order);
+				boolean hasPrev = start > 0;
+				int prevStart = Math.max(0, start - count);
+				int nextStart = start + count;
+				int pageNumber = (start / count) + 1;
+
+				StringBuffer pagingHtml = new StringBuffer();
+				pagingHtml.append("<a href=\"#\" onclick=\"page(0,").append(count).append(");\">&lt;&lt;</a>&nbsp;");
+				if (hasPrev) {
+					pagingHtml.append("<a href=\"#\" onclick=\"page(").append(prevStart).append(",").append(count).append(");\">&lt;</a>&nbsp;");
+				} else {
+					pagingHtml.append("&lt;&nbsp;");
+				}
+				if (hasNext) {
+					pagingHtml.append("<a href=\"#\" onclick=\"page(").append(nextStart).append(",").append(count).append(");\">&gt;</a>");
+				} else {
+					pagingHtml.append("&gt;");
+				}
+
+				StringBuffer pagingItemHtml = new StringBuffer();
+				pagingItemHtml.append("Page ").append(pageNumber);
+
+				request.setAttribute("paging", pagingHtml.toString());
+				request.setAttribute("pagingItem", pagingItemHtml.toString());
+				request.setAttribute("dlogList", items);
+				request.setAttribute("dlog_start", new Integer(start));
+
+				for (int i = 0; i < items.length; i++) {
+					DlogList dlogList = (DlogList) items[i];
+					log.info("-dlogList.getCode()- " + dlogList.getCode());
+					log.info("-dlogList.getSource()- " + dlogList.getSource());
+				}
+				log.info("------------ DLOGLIST --------------- (no total count) FROM DlogList dlog WHERE 1=1 " + dlog_filterSb + " order " + order);
+			} else {
+				// ==== CR#2: initial load - tidak query ke tabel DLOG sama sekali ====
+				request.setAttribute("dlogList", null);
+				request.setAttribute("paging", null);
+				request.setAttribute("pagingItem", null);
+				request.setAttribute("dlog_start", new Integer(0));
+			}
 		} catch (Exception e) {
 			e.printStackTrace();
 			log.error(e.getMessage(), e);
@@ -620,7 +756,7 @@ public class DlogAction extends Action {
 								if (buf.remaining() > 0) {
 									msg.put("Broker Log", new String(buf.array(), buf.position(), buf.remaining()).trim());
 								}
-								log.info("3[][][][]][][]");
+								log.info("3[][][]][][]");
 							}
 						} else {
 							log.info("4[][][][][][]");
@@ -742,7 +878,7 @@ public class DlogAction extends Action {
 
 	private synchronized static LinkedHashMap getTypeList(PersistanceServiceInterface psi, id.co.hanoman.service.List typeList) throws Exception {
 		typeList = psi.query(Constant.MWLOG_DS, "FROM DlogMd WHERE 1=1 order by type ASC ", null, 0, 0);
-		System.out.println("-------------------------------------------------------- getTypeList(PersistanceServiceInterface psi, id.co.hanoman.service.List typeList) ----------------------------------------------------");
+		// log.info("-------------------------------------------------------- getTypeList(PersistanceServiceInterface psi, id.co.hanoman.service.List typeList) ----------------------------------------------------");
 		Object[] typeItems = typeList.getItems();
 		typeMap = new LinkedHashMap();
 		for (Object typeObj : typeItems) {
@@ -757,7 +893,7 @@ public class DlogAction extends Action {
 			// if(o instanceof String) {
 			// String key = (String) o;
 			// if(typeMap.containsKey(key)) {
-			// System.out.println("duplicate");
+			// log.info("duplicate");
 			// typeMap = new LinkedHashMap();
 			// typeMap.put(dlogMd.getType(), cList);
 			// }
@@ -766,7 +902,7 @@ public class DlogAction extends Action {
 			// }
 			map.put(dlogMd.getType(), cList);
 			typeMap.putAll(map);
-			// System.out.println(("Key::"+typeMap.keySet()+"Value::"+typeMap.values()));
+			// log.info(("Key::"+typeMap.keySet()+"Value::"+typeMap.values()));
 		}
 		return typeMap;
 	}