reverb.twig 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. {{ header }}{{ column_left }}
  2. <div id="content">
  3. <div class="page-header">
  4. <div class="container-fluid">
  5. <div class="pull-right">
  6. <button type="submit" form="form-reverb" class="btn btn-primary">
  7. <i class="fa fa-floppy-o"></i> {{ button_save }}
  8. </button>
  9. <a href="{{ cancel }}" class="btn btn-default">
  10. <i class="fa fa-reply"></i> {{ button_cancel }}
  11. </a>
  12. </div>
  13. <h1>{{ heading_title }}</h1>
  14. <ul class="breadcrumb">
  15. {% for breadcrumb in breadcrumbs %}
  16. <li><a href="{{ breadcrumb.href }}">{{ breadcrumb.text }}</a></li>
  17. {% endfor %}
  18. </ul>
  19. </div>
  20. </div>
  21. <div class="container-fluid">
  22. {% if error_warning %}
  23. <div class="alert alert-danger alert-dismissible">
  24. <i class="fa fa-exclamation-circle"></i> {{ error_warning }}
  25. <button type="button" class="close" data-dismiss="alert">&times;</button>
  26. </div>
  27. {% endif %}
  28. {% if success %}
  29. <div class="alert alert-success alert-dismissible">
  30. <i class="fa fa-check-circle"></i> {{ success }}
  31. <button type="button" class="close" data-dismiss="alert">&times;</button>
  32. </div>
  33. {% endif %}
  34. <div class="panel panel-default">
  35. <div class="panel-heading">
  36. <h3 class="panel-title"><i class="fa fa-cog"></i> {{ heading_title }}</h3>
  37. </div>
  38. <div class="panel-body">
  39. <ul class="nav nav-tabs">
  40. <li class="active"><a href="#tab-settings" data-toggle="tab">{{ tab_settings }}</a></li>
  41. <li><a href="#tab-categories" data-toggle="tab">{{ tab_categories }}</a></li>
  42. <li><a href="#tab-reverb-cats" data-toggle="tab">{{ tab_reverb_cats }}</a></li>
  43. <li><a href="#tab-log" data-toggle="tab">{{ tab_log }}</a></li>
  44. </ul>
  45. <form action="{{ action }}" method="post" enctype="multipart/form-data" id="form-reverb" class="form-horizontal">
  46. <!-- ================================================================ -->
  47. <!-- TAB: Settings -->
  48. <!-- ================================================================ -->
  49. <div class="tab-content">
  50. <div class="tab-pane active" id="tab-settings">
  51. <h4>{{ text_api_settings }}</h4>
  52. <div class="form-group required">
  53. <label class="col-sm-2 control-label">{{ entry_api_token }}</label>
  54. <div class="col-sm-10">
  55. <input type="text" name="module_reverb_api_token"
  56. value="{{ module_reverb_api_token }}"
  57. class="form-control" placeholder="e.g. abc123..." />
  58. <span class="help-block">{{ help_api_token }}</span>
  59. </div>
  60. </div>
  61. <div class="form-group">
  62. <label class="col-sm-2 control-label">{{ entry_status }}</label>
  63. <div class="col-sm-10">
  64. <select name="module_reverb_status" class="form-control">
  65. <option value="1" {% if module_reverb_status %}selected{% endif %}>{{ text_enabled }}</option>
  66. <option value="0" {% if not module_reverb_status %}selected{% endif %}>{{ text_disabled }}</option>
  67. </select>
  68. </div>
  69. </div>
  70. <div class="form-group">
  71. <label class="col-sm-2 control-label">{{ entry_sync_direction }}</label>
  72. <div class="col-sm-10">
  73. <select name="module_reverb_sync_direction" class="form-control">
  74. <option value="push" {% if module_reverb_sync_direction == 'push' %}selected{% endif %}>{{ text_sync_push }}</option>
  75. <option value="both" {% if module_reverb_sync_direction == 'both' %}selected{% endif %}>{{ text_sync_both }}</option>
  76. </select>
  77. </div>
  78. </div>
  79. <h4>{{ text_shipping_settings }}</h4>
  80. <div class="form-group">
  81. <label class="col-sm-2 control-label">{{ entry_shipping_domestic }}</label>
  82. <div class="col-sm-4">
  83. <div class="input-group">
  84. <span class="input-group-addon">$</span>
  85. <input type="text" name="module_reverb_shipping_domestic"
  86. value="{{ module_reverb_shipping_domestic }}"
  87. class="form-control" />
  88. </div>
  89. <span class="help-block">{{ help_shipping_domestic }}</span>
  90. </div>
  91. </div>
  92. <div class="form-group">
  93. <label class="col-sm-2 control-label">{{ entry_shipping_international }}</label>
  94. <div class="col-sm-4">
  95. <div class="input-group">
  96. <span class="input-group-addon">$</span>
  97. <input type="text" name="module_reverb_shipping_international"
  98. value="{{ module_reverb_shipping_international }}"
  99. class="form-control" />
  100. </div>
  101. <span class="help-block">{{ help_shipping_international }}</span>
  102. </div>
  103. </div>
  104. <h4>{{ text_sync_settings }}</h4>
  105. <div class="form-group">
  106. <label class="col-sm-2 control-label">Categories</label>
  107. <div class="col-sm-10">
  108. <select name="module_reverb_sync_categories[]" multiple class="form-control" size="10">
  109. {% for category in categories %}
  110. <option value="{{ category.category_id }}"
  111. {% if category.category_id in module_reverb_sync_categories %}selected{% endif %}>
  112. {{ category.name|raw }}
  113. </option>
  114. {% endfor %}
  115. </select>
  116. <span class="help-block">{{ help_sync_categories }}</span>
  117. </div>
  118. </div>
  119. <h4>{{ text_manual_sync }}</h4>
  120. <div class="form-group">
  121. <div class="col-sm-offset-2 col-sm-10">
  122. <button type="button" id="btn-sync-now" class="btn btn-warning">
  123. <i class="fa fa-refresh"></i> {{ button_sync_now }}
  124. </button>
  125. <span id="sync-result" class="help-block" style="display:none;"></span>
  126. </div>
  127. </div>
  128. <h4>{{ text_order_import }}</h4>
  129. <div class="form-group">
  130. <label class="col-sm-2 control-label">{{ entry_order_stores }}</label>
  131. <div class="col-sm-10">
  132. <div class="well well-sm" style="height:150px;overflow:auto;">
  133. {% for store in stores %}
  134. <div class="checkbox">
  135. <label>
  136. <input type="checkbox" name="module_reverb_order_stores[]" value="{{ store.store_id }}"
  137. {% if store.store_id in module_reverb_order_stores %}checked{% endif %}>
  138. {% if store.store_id == 0 %}<b>(Default)</b>{% else %}{{ store.name }}{% endif %}
  139. </label>
  140. </div>
  141. {% endfor %}
  142. </div>
  143. <a href="#" onclick="$(this).closest('.form-group').find(':checkbox').prop('checked',true);return false;">{{ text_select_all }}</a>
  144. /
  145. <a href="#" onclick="$(this).closest('.form-group').find(':checkbox').prop('checked',false);return false;">{{ text_unselect_all }}</a>
  146. <span class="help-block">{{ help_order_stores }}</span>
  147. </div>
  148. </div>
  149. <div class="form-group">
  150. <label class="col-sm-2 control-label">{{ entry_default_qty }}</label>
  151. <div class="col-sm-2">
  152. <input type="number" name="module_reverb_default_qty" min="1"
  153. value="{{ module_reverb_default_qty }}" class="form-control" />
  154. <span class="help-block">{{ help_default_qty }}</span>
  155. </div>
  156. </div>
  157. <div class="form-group">
  158. <div class="col-sm-offset-2 col-sm-10">
  159. <button type="button" id="btn-import-orders" class="btn btn-info">
  160. <i class="fa fa-download"></i> {{ button_import_orders }}
  161. </button>
  162. <span id="import-result" class="help-block" style="display:none;"></span>
  163. </div>
  164. </div>
  165. </div><!-- #tab-settings -->
  166. <!-- ================================================================ -->
  167. <!-- TAB: Category Mapping -->
  168. <!-- ================================================================ -->
  169. <div class="tab-pane" id="tab-categories">
  170. <p class="help-block">{{ text_category_mapping_help }}</p>
  171. {% if module_reverb_sync_categories is empty %}
  172. <div class="alert alert-info">{{ text_no_categories }}</div>
  173. {% else %}
  174. <table class="table table-bordered table-hover">
  175. <thead>
  176. <tr>
  177. <th>{{ column_oc_category }}</th>
  178. <th>{{ column_reverb_category }}</th>
  179. </tr>
  180. </thead>
  181. <tbody>
  182. {% for category in categories %}
  183. {% if category.category_id in module_reverb_sync_categories %}
  184. <tr>
  185. <td>{{ category.name|raw }}</td>
  186. <td>
  187. <select name="module_reverb_category_mappings[{{ category.category_id }}]"
  188. class="form-control">
  189. <option value="">-- Select Reverb Category --</option>
  190. {% for group_name, group_cats in reverb_categories_grouped %}
  191. <optgroup label="{{ group_name }}">
  192. {% for rc in group_cats %}
  193. <option value="{{ rc.uuid }}"
  194. {% if module_reverb_category_mappings[category.category_id] is defined
  195. and module_reverb_category_mappings[category.category_id] == rc.uuid %}
  196. selected
  197. {% endif %}>
  198. {{ rc.full_name|default(rc.name) }}
  199. </option>
  200. {% endfor %}
  201. </optgroup>
  202. {% endfor %}
  203. </select>
  204. </td>
  205. </tr>
  206. {% endif %}
  207. {% endfor %}
  208. </tbody>
  209. </table>
  210. {% endif %}
  211. </div><!-- #tab-categories -->
  212. <!-- ================================================================ -->
  213. <!-- TAB: Reverb Categories -->
  214. <!-- ================================================================ -->
  215. <div class="tab-pane" id="tab-reverb-cats">
  216. <p class="help-block">{{ text_reverb_cats_help }}</p>
  217. <div style="margin-bottom:12px;">
  218. <button type="button" id="btn-refresh-cats" class="btn btn-primary btn-sm">
  219. <i class="fa fa-refresh"></i> {{ button_refresh_cats }}
  220. </button>
  221. <span id="refresh-cats-result" style="margin-left:10px;"></span>
  222. </div>
  223. <div style="margin-bottom:10px;">
  224. <input type="text" id="cat-filter" class="form-control"
  225. placeholder="{{ text_filter_cats }}" style="max-width:400px;" />
  226. </div>
  227. <div style="max-height:500px;overflow-y:auto;">
  228. <table class="table table-bordered table-hover table-condensed" id="reverb-cats-table">
  229. <thead>
  230. <tr>
  231. <th>{{ column_cat_name }}</th>
  232. <th style="width:310px;">{{ column_cat_uuid }}</th>
  233. </tr>
  234. </thead>
  235. <tbody>
  236. {% for cat in reverb_categories_flat %}
  237. <tr>
  238. <td>{{ cat.full_name|default(cat.name) }}</td>
  239. <td><code>{{ cat.uuid }}</code></td>
  240. </tr>
  241. {% else %}
  242. <tr><td colspan="2" class="text-muted text-center">No categories loaded yet. Click Refresh to fetch from Reverb.</td></tr>
  243. {% endfor %}
  244. </tbody>
  245. </table>
  246. </div>
  247. </div><!-- #tab-reverb-cats -->
  248. <!-- ================================================================ -->
  249. <!-- TAB: Sync Log -->
  250. <!-- ================================================================ -->
  251. <div class="tab-pane" id="tab-log">
  252. <div style="margin-bottom:10px;">
  253. <button type="button" id="btn-clear-log" class="btn btn-danger btn-sm">
  254. <i class="fa fa-trash-o"></i> {{ button_clear_log }}
  255. </button>
  256. <span id="clear-log-result" style="margin-left:10px;"></span>
  257. </div>
  258. {% if sync_log is empty %}
  259. <p>{{ text_no_log }}</p>
  260. {% else %}
  261. <table class="table table-bordered table-hover table-striped">
  262. <thead>
  263. <tr>
  264. <th>{{ column_date }}</th>
  265. <th>{{ column_product }}</th>
  266. <th>{{ column_direction }}</th>
  267. <th>{{ column_status }}</th>
  268. <th>{{ column_message }}</th>
  269. </tr>
  270. </thead>
  271. <tbody>
  272. {% for entry in sync_log %}
  273. <tr class="{{ entry.status == 'error' ? 'danger' : '' }}">
  274. <td>{{ entry.created_at }}</td>
  275. <td>{{ entry.product_name ?? entry.product_id }}</td>
  276. <td>{{ entry.direction == 'push' ? text_push : text_pull }}</td>
  277. <td>
  278. <span class="label label-{{ entry.status == 'success' ? 'success' : 'danger' }}">
  279. {{ entry.status == 'success' ? text_log_success : text_error }}
  280. </span>
  281. </td>
  282. <td>{{ entry.message }}</td>
  283. </tr>
  284. {% endfor %}
  285. </tbody>
  286. </table>
  287. {% endif %}
  288. </div><!-- #tab-log -->
  289. </div><!-- .tab-content -->
  290. </form>
  291. </div><!-- .panel-body -->
  292. </div><!-- .panel -->
  293. </div><!-- .container-fluid -->
  294. </div><!-- #content -->
  295. <script>
  296. $(function() {
  297. $('#btn-sync-now').on('click', function() {
  298. var $btn = $(this);
  299. var $result = $('#sync-result');
  300. $btn.prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i> Syncing...');
  301. $result.hide();
  302. var _p = new URLSearchParams(window.location.search);
  303. var _syncUrl = 'index.php?route=extension/module/reverb/sync&user_token=' + encodeURIComponent(_p.get('user_token') || '');
  304. $.ajax({
  305. url: _syncUrl,
  306. type: 'GET',
  307. dataType: 'json',
  308. success: function(data) {
  309. if (data.success) {
  310. $result.removeClass('text-danger').addClass('text-success').text(data.message).show();
  311. } else {
  312. $result.removeClass('text-success').addClass('text-danger').text(data.error).show();
  313. }
  314. },
  315. error: function() {
  316. $result.removeClass('text-success').addClass('text-danger').text('Sync request failed.').show();
  317. },
  318. complete: function() {
  319. $btn.prop('disabled', false).html('<i class="fa fa-refresh"></i> {{ button_sync_now }}');
  320. }
  321. });
  322. });
  323. $('#btn-import-orders').on('click', function() {
  324. var $btn = $(this);
  325. var $result = $('#import-result');
  326. $btn.prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i> Importing...');
  327. $result.hide();
  328. var _p = new URLSearchParams(window.location.search);
  329. var _importUrl = 'index.php?route=extension/module/reverb/importOrders&user_token=' + encodeURIComponent(_p.get('user_token') || '');
  330. $.ajax({
  331. url: _importUrl,
  332. type: 'GET',
  333. dataType: 'json',
  334. success: function(data) {
  335. if (data.success) {
  336. $result.removeClass('text-danger').addClass('text-success').text(data.message).show();
  337. } else {
  338. $result.removeClass('text-success').addClass('text-danger').text(data.error).show();
  339. }
  340. },
  341. error: function() {
  342. $result.removeClass('text-success').addClass('text-danger').text('Import request failed.').show();
  343. },
  344. complete: function() {
  345. $btn.prop('disabled', false).html('<i class="fa fa-download"></i> {{ button_import_orders }}');
  346. }
  347. });
  348. });
  349. // Reverb Categories tab — refresh button
  350. $('#btn-refresh-cats').on('click', function() {
  351. var $btn = $(this);
  352. var $result = $('#refresh-cats-result');
  353. var $tbody = $('#reverb-cats-table tbody');
  354. $btn.prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i> Refreshing…');
  355. $result.text('');
  356. var _p = new URLSearchParams(window.location.search);
  357. var _url = 'index.php?route=extension/module/reverb/refreshCategories&user_token=' + encodeURIComponent(_p.get('user_token') || '');
  358. $.ajax({
  359. url: _url, type: 'GET', dataType: 'json',
  360. success: function(data) {
  361. if (data.success) {
  362. $result.removeClass('text-danger').addClass('text-success').text(data.message);
  363. var rows = '';
  364. $.each(data.categories, function(i, cat) {
  365. rows += '<tr><td>' + $('<span>').text(cat.full_name || cat.name).html() + '</td>'
  366. + '<td><code>' + $('<span>').text(cat.uuid).html() + '</code></td></tr>';
  367. });
  368. $tbody.html(rows || '<tr><td colspan="2" class="text-muted text-center">No categories returned.</td></tr>');
  369. $('#cat-filter').trigger('input');
  370. } else {
  371. $result.removeClass('text-success').addClass('text-danger').text(data.error || 'Refresh failed.');
  372. }
  373. },
  374. error: function() {
  375. $result.removeClass('text-success').addClass('text-danger').text('Request failed.');
  376. },
  377. complete: function() {
  378. $btn.prop('disabled', false).html('<i class="fa fa-refresh"></i> {{ button_refresh_cats }}');
  379. }
  380. });
  381. });
  382. // Reverb Categories tab — live filter
  383. $('#cat-filter').on('input', function() {
  384. var q = $(this).val().toLowerCase();
  385. $('#reverb-cats-table tbody tr').each(function() {
  386. $(this).toggle(!q || $(this).text().toLowerCase().indexOf(q) !== -1);
  387. });
  388. });
  389. $('#btn-clear-log').on('click', function() {
  390. if (!confirm('Clear the entire sync log?')) { return; }
  391. var $btn = $(this);
  392. var $result = $('#clear-log-result');
  393. $btn.prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i>');
  394. var _p = new URLSearchParams(window.location.search);
  395. var _clearUrl = 'index.php?route=extension/module/reverb/clearLog&user_token=' + encodeURIComponent(_p.get('user_token') || '');
  396. $.ajax({
  397. url: _clearUrl,
  398. type: 'GET',
  399. dataType: 'json',
  400. success: function(data) {
  401. if (data.success) {
  402. $result.removeClass('text-danger').addClass('text-success').text(data.message);
  403. setTimeout(function() { window.location.reload(); }, 800);
  404. } else {
  405. $result.removeClass('text-success').addClass('text-danger').text(data.error);
  406. }
  407. },
  408. error: function() {
  409. $result.removeClass('text-success').addClass('text-danger').text('Request failed.');
  410. },
  411. complete: function() {
  412. $btn.prop('disabled', false).html('<i class="fa fa-trash-o"></i> {{ button_clear_log }}');
  413. }
  414. });
  415. });
  416. });
  417. </script>
  418. {{ footer }}