/** * Button manager - centralizes button config objects for the action bar * * Reads (via globals): * SFE.BatchEditManager - .isSessionActive, .isEnabled * SFE.Api - .apiCall * SFE.PostLockManager - .ensureLock, .handleLockedError * SFE.Context - .uuidMap, .pageRevisionToken * SFE.SaveHelpers - .updatePageRevisionToken * SFE.showInlineSuccess - set by SaveManager * SFE.closeDraftPreview - set by DraftManager * SFE.ManagerData - .postId, .hasPro, .canManageDrafts * * Exposes: SFE.ButtonManager (class - instantiated by frontend-inline-edit.js) * { getLoadingConfig, getEditButtons, getEditDraftConfig, getClosePreviewConfig, * getCloseConfig, getApproveDraftConfig, getDiscardDraftConfig, * getDraftPreviewButtons, getMediaSaveButtons } */ (function() { 'use strict'; window.MWP = window.MWP || {}; window.MWP.SFE = window.MWP.SFE || {}; const SFE = window.MWP.SFE; SFE.ManagerData = SFE.ManagerData || {}; class ButtonManager { constructor(deps) { // Store dependencies passed from the main app this.perms = deps.perms; // These methods will be assigned later by the main app this.handleInlineSave = deps.handleInlineSave || null; this.closeInPlaceEditor = deps.closeInPlaceEditor || null; this.editPendingDraft = deps.editPendingDraft || null; this.closeDraftPreview = deps.closeDraftPreview || null; } /** * Get loading button config */ getLoadingConfig(text) { return { text: text || 'Saving...', className: 'mwp-sfe-btn mwp-sfe-btn-primary-inline', disabled: true, loading: true }; } /** * Get the Save + Cancel button configs for the 'edit' state. * Save label reflects batch-session state and publish permissions. * Buttons store themselves on the bar (_saveBtn / _cancelBtn) via storeAs * so that editors can look them up after updateState() returns. */ getEditButtons() { let saveText; if (this.perms.can_publish) { saveText = 'Save Changes'; } else if (this.perms.can_draft) { saveText = 'Submit for Review'; } else { saveText = 'Save'; } return { buttons: [ { text: saveText, className: 'mwp-sfe-btn mwp-sfe-btn-primary-inline', storeAs: 'saveBtn' }, { text: 'Cancel', className: 'mwp-sfe-btn mwp-sfe-btn-secondary-inline', storeAs: 'cancelBtn' } ] }; } /** * Get Edit Draft button config */ getEditDraftConfig(bar, element, handlers, uuid) { return { text: 'Edit', className: 'mwp-sfe-btn mwp-sfe-btn-secondary-inline', onClick: (e) => { e.preventDefault(); e.stopPropagation(); if (this.editPendingDraft) this.editPendingDraft(bar, element, handlers, uuid); } }; } /** * Get Close Preview button config */ getClosePreviewConfig(bar, element, handlers, uuid) { return { text: 'Close', className: 'mwp-sfe-btn mwp-sfe-btn-secondary-inline', onClick: (e) => { e.preventDefault(); e.stopPropagation(); if (this.closeDraftPreview) this.closeDraftPreview(bar, element, uuid); } }; } /** * Get Close button config (for error states) */ getCloseConfig(bar, element, handlers, uuid) { return this.getClosePreviewConfig(bar, element, handlers, uuid); // Reuse logic } /** * Get Approve Draft button config. * Calls the Pro draft-decision route, then refreshes the element HTML * via showInlineSuccess so the live element reflects the approved content. */ getApproveDraftConfig(bar, element, handlers, uuid) { return { text: 'Approve', className: 'mwp-sfe-btn mwp-sfe-btn-primary-inline', onClick: async (e) => { e.preventDefault(); e.stopPropagation(); const btn = e.currentTarget; const apiCall = SFE.Api?.apiCall; if ( ! apiCall ) return; const ctx = SFE.Context; const version = ctx?.uuidMap?.[ uuid ]?.pending_info?.version ?? null; if ( version === null ) { console.warn( 'FrontEdit: No pending_info.version for', uuid ); return; } if (!await SFE.PostLockManager.ensureLock()) return; // Lock the full UI const { lockSaveUI, unlockSaveUI } = SFE.SaveHelpers || {}; bar.querySelectorAll( '.mwp-sfe-btn' ).forEach( b => { if ( b !== btn ) b.disabled = true; } ); if ( lockSaveUI ) { lockSaveUI( btn ); } else { btn.disabled = true; btn.setAttribute( 'mwp-sfe-btn-loading', 'true' ); } const restoreBtn = () => { if ( unlockSaveUI ) { unlockSaveUI( btn ); } else { btn.disabled = false; btn.removeAttribute( 'mwp-sfe-btn-loading' ); } // Re-enable the other bar buttons so the user can try again. bar.querySelectorAll( '.mwp-sfe-btn' ).forEach( b => { b.disabled = false; b.removeAttribute( 'mwp-sfe-btn-loading' ); } ); }; const reloadAfterRefreshFailure = (message) => { if (SFE.SaveHelpers && typeof SFE.SaveHelpers.reloadAfterRefreshFailure === 'function') { SFE.SaveHelpers.reloadAfterRefreshFailure(message); return; } if (message) console.error(message); window.location.reload(); }; const doApprove = async ( includeToken ) => { const payload = { post_id: SFE.ManagerData.postId, element_uuid: uuid, version, decision: 'approve', }; if ( includeToken ) { payload.page_revision_token = ctx?.pageRevisionToken || 0; } return apiCall( '/pro/decide-draft', payload ); }; try { const result = await doApprove( true ); // Keep the client token current so subsequent saves on this // session don't false-conflict against the revision we just created. SFE.SaveHelpers?.updatePageRevisionToken( result ); // Fetch the freshly-rendered approved block HTML from the current page render and swap it in. const blockHTML = await SFE.SaveHelpers.fetchRenderedBlockHTML( uuid, { force: true } ); if ( blockHTML ) { // Reuse the save-success path: re-enables hover, resets state, re-attaches. const mockState = { element, uuid, actionsContainer: bar, handler: handlers.find( h => h.capability === 'edit' ) || handlers[0], }; // Update uuidMap so the element is no longer flagged as pending. if ( ctx?.uuidMap?.[ uuid ] ) { ctx.uuidMap[ uuid ].is_pending = false; delete ctx.uuidMap[ uuid ].pending_info; } SFE.showInlineSuccess( mockState, 'Draft Approved', blockHTML, false ); } else { reloadAfterRefreshFailure( 'FrontEdit: Draft approval succeeded but refreshed block HTML was unavailable.' ); return; } } catch ( err ) { if ( err.message === 'POST_LOCKED' ) { await SFE.PostLockManager.handleLockedError( err ); restoreBtn(); return; } if ( err.message === 'BLOCK_HTML_REFRESH_FAILED' ) { reloadAfterRefreshFailure( 'FrontEdit: Draft approval succeeded but refreshed block HTML could not be extracted from the post render.' ); return; } if ( err.message === 'REVISION_CONFLICT' ) { const saveAnyway = confirm( 'This page was updated by another user since you started editing. ' + 'Approving now may overwrite their recent changes.\n\n' + 'Press OK to approve anyway, or Cancel to refresh the page.' ); if ( ! saveAnyway ) { restoreBtn(); window.location.reload(); return; } // Retry without the conflict token so the server force-approves. try { await doApprove( false ); const blockHTML = await SFE.SaveHelpers.fetchRenderedBlockHTML( uuid, { force: true } ); if ( blockHTML ) { const mockState = { element, uuid, actionsContainer: bar, handler: handlers.find( h => h.capability === 'edit' ) || handlers[0], }; if ( ctx?.uuidMap?.[ uuid ] ) { ctx.uuidMap[ uuid ].is_pending = false; delete ctx.uuidMap[ uuid ].pending_info; } SFE.showInlineSuccess( mockState, 'Draft Approved', blockHTML, false ); setTimeout( () => window.location.reload(), SFE.TIMING?.EDIT_DELAY || 300 ); } else { reloadAfterRefreshFailure( 'FrontEdit: Draft approval retry succeeded but refreshed block HTML was unavailable.' ); return; } } catch ( retryErr ) { if ( retryErr.message === 'BLOCK_HTML_REFRESH_FAILED' ) { reloadAfterRefreshFailure( 'FrontEdit: Draft approval retry succeeded but refreshed block HTML could not be extracted from the post render.' ); return; } console.error( 'FrontEdit: Approve retry failed', retryErr ); restoreBtn(); } return; } console.error( 'FrontEdit: Approve failed', err ); if ( err.message && err.message.indexOf( '403' ) !== -1 ) { alert( 'You no longer have permission to approve drafts. Your publish access may have been changed. Please refresh the page.' ); } restoreBtn(); } } }; } /** * Get Discard Draft button config. * Calls the Pro draft-decision route with `deny`, then shows a red banner, * restoring the original live element without changing post content. */ getDiscardDraftConfig(bar, element, handlers, uuid) { return { text: 'Discard', className: 'mwp-sfe-btn mwp-sfe-btn-secondary-inline', onClick: async (e) => { e.preventDefault(); e.stopPropagation(); const btn = e.currentTarget; const apiCall = SFE.Api?.apiCall; if ( ! apiCall ) return; const ctx = SFE.Context; const version = ctx?.uuidMap?.[ uuid ]?.pending_info?.version ?? null; if ( version === null ) { console.warn( 'FrontEdit: No pending_info.version for', uuid ); return; } // Lock the full UI const { lockSaveUI, unlockSaveUI } = SFE.SaveHelpers || {}; bar.querySelectorAll( '.mwp-sfe-btn' ).forEach( b => { if ( b !== btn ) b.disabled = true; } ); if ( lockSaveUI ) { lockSaveUI( btn ); } else { btn.disabled = true; btn.setAttribute( 'mwp-sfe-btn-loading', 'true' ); } try { await apiCall( '/pro/decide-draft', { post_id: SFE.ManagerData.postId, element_uuid: uuid, version, decision: 'deny', } ); // Update uuidMap so the element is no longer flagged as pending. if ( ctx?.uuidMap?.[ uuid ] ) { ctx.uuidMap[ uuid ].is_pending = false; delete ctx.uuidMap[ uuid ].pending_info; } // showInlineSuccess with isDraft=true restores via editorState.originalOuterHTML. // The original (live) element was stashed in bar._originalElement by DraftManager // when the draft was swapped in - use its outerHTML as the restore source. const mockState = { element, uuid, actionsContainer: bar, originalOuterHTML: bar._originalElement?.outerHTML ?? element.outerHTML, clearPending: true, handler: handlers.find( h => h.capability === 'edit' ) || handlers[0], }; SFE.showInlineSuccess( mockState, 'Draft Discarded', null, true, 'discard' ); } catch ( err ) { console.error( 'FrontEdit: Discard failed', err ); if ( err.message && err.message.indexOf( '403' ) !== -1 ) { alert( 'You no longer have permission to discard drafts. Your publish access may have been changed. Please refresh the page.' ); } // Unlock the full UI on error so the user can try again. if ( unlockSaveUI ) { unlockSaveUI( btn ); } else { btn.disabled = false; btn.removeAttribute( 'mwp-sfe-btn-loading' ); } bar.querySelectorAll( '.mwp-sfe-btn' ).forEach( b => { b.disabled = false; b.removeAttribute( 'mwp-sfe-btn-loading' ); } ); } } }; } /** * Get complete content config for draft preview (loaded state). * * Returns { header, buttons } so the action bar can render a * draft-specific header that updates on button hover. * * Permission model: * - can_publish – Approve + Discard + (pro: Edit Draft) + Close Preview * - not publish – Close Preview only (view the draft but cannot decide) */ getDraftPreviewButtons(bar, element, handlers, uuid) { const hasPro = !! ( SFE.ManagerData?.hasPro ); const canManageDrafts = SFE.ManagerData?.canManageDrafts === true; const elementType = ( handlers && handlers[0] && handlers[0].elementType ) || 'Element'; const defaultHeader = `${elementType} • View pending draft.`; const buttons = []; if ( canManageDrafts ) { const approveConfig = this.getApproveDraftConfig( bar, element, handlers, uuid ); approveConfig.hoverHeader = `${elementType} • Approve and publish this draft.`; buttons.push( approveConfig ); const discardConfig = this.getDiscardDraftConfig( bar, element, handlers, uuid ); discardConfig.hoverHeader = `${elementType} • Discard this draft.`; buttons.push( discardConfig ); // Pro adds Edit Draft so the content can be changed before approval. if ( hasPro ) { const editConfig = this.getEditDraftConfig( bar, element, handlers, uuid ); editConfig.hoverHeader = `${elementType} • Edit this draft.`; buttons.push( editConfig ); } } // View-only users may close their preview but cannot act on the draft. const closeConfig = this.getClosePreviewConfig( bar, element, handlers, uuid ); closeConfig.hoverHeader = `${elementType} • Close draft preview.`; buttons.push( closeConfig ); return { header: defaultHeader, buttons }; } /** * Get Media Save/Back buttons */ getMediaSaveButtons(onSave, onBack) { let saveText = this.perms.can_publish ? 'Save Changes' : 'Submit for Review'; return [ { text: saveText, className: 'mwp-sfe-btn mwp-sfe-btn-primary-inline', onClick: (e) => { e.preventDefault(); onSave(e); } }, { text: 'Back', className: 'mwp-sfe-btn mwp-sfe-btn-secondary-inline', onClick: (e) => { e.preventDefault(); onBack(e); } } ]; } } // Expose globally SFE.ButtonManager = ButtonManager; })(); Mobil Donor - Ambulance Pintar

Ambulance Pintar

Make an Appointment

Edit Template

Mobil Donor

Home - Detail Produk

Produk Terbaik

  • All Post
  • Ambulance
  • Blog
  • Cherry
  • Daihatsu
  • DFSK
  • Hyundai
  • Isuzu
  • Mercy
  • Mitsubishi
  • Non Ambulance
  • Suzuki
  • Toyota
  • Wuling
Suzuki Ertiga

Spesifikasi Lengkap Tekan untuk melihat deksripsi dari spesifikasi yang ditawarkan Jenazah Multifungsi SPESIFIKASI AMBULANCE JENAZAH : LED Light Bar Oval...

Suzuki APV

Spesifikasi Lengkap Tekan untuk melihat deksripsi dari spesifikasi yang ditawarkan Jenazah Multifungsi SPESIFIKASI AMBULANCE JENAZAH : LED Light Bar Oval...

Suzuki New Carry

Spesifikasi Lengkap Tekan untuk melihat deksripsi dari spesifikasi yang ditawarkan Jenazah Multifungsi SPESIFIKASI AMBULANCE JENAZAH : LED Light Bar Oval...

Butuh Bantuan?

Lorem Ipsum is simply dumy text of the printing typesetting industry beautiful worldlorem ipsum.

Mobil Donor

Deskripsi

Mobil Donor adalah kendaraan yang dirancang khusus untuk memfasilitasi proses pengumpulan darah atau organ dari pendonor. Kendaraan ini dilengkapi dengan fasilitas medis dan ruang yang nyaman untuk melakukan prosedur donor dengan aman. Biasanya digunakan oleh organisasi atau lembaga kemanusiaan yang mengelola kegiatan donor darah, seperti Palang Merah Indonesia (PMI), atau oleh rumah sakit untuk mengumpulkan donor organ. Mobil donor memungkinkan proses donor dilakukan dengan cepat dan efektif di berbagai lokasi, seperti di tengah keramaian atau di daerah yang sulit dijangkau. Dengan fasilitas medis yang lengkap dan tenaga medis terlatih, mobil donor memberikan kenyamanan dan keamanan bagi pendonor.

SPESIFIKASI AMBULANCE JENAZAH :

SPESIFIKASI AMBULANCE TRANSPORT STANDAR PLUS:

  1. 2 tabung oksigen Im3
  2. 2 buah regulator standar
  3. 1 set selang oksigen tekanan tinggi
  4. 1 set pengatur oksigen dengan kran ON/Off
  5. 1 buah keluaran dinding dengan tulisan”Oxygen”
  6. 1 Set Flowmeter dan Humidifier
  7. 1 buah nassal cannula
  8. 1 buah masker oksige

SPESIFIKASI AMBULANCE STANDAR :

SPESIFIKASI AMBULANCE STANDAR 1 :

  1. tabung oksigen 1m^3 
  2. 1 buah regulator standar 
  3. 1 Set Flowmeter dan Humidifier 
  4. 1 buah nassal cannula 
  5. 1 buah masker oksigen

SPESIFIKASI EMERGENCY VVIP :

  1. 2 tabung oksigen 1m3
  2. 2 buah regulator standar
  3. 1 set selang oksigen tekanan tinggi
  4. 1 set pengatur oksigen dengan kran ON/Off
  5. 1 buah keluaran dinding dengan tulisan”Oxygen”
  6. 1 Set Flowmeter dan Humidifier
  7. 1 buah nassal cannula
  8. 1 buah masker oksigen

SPESIFIKASI EMERGENCY VVIP :

  1. 2 tabung oksigen 1m3
  2. 2 buah regulator standar
  3. 1 set selang oksigen tekanan tinggi
  4. 1 set pengatur oksigen dengan kran ON/Off
  5. 1 buah keluaran dinding dengan tulisan”Oxygen”
  6. 1 Set Flowmeter dan Humidifier
  7. 1 buah nassal cannula
  8. 1 buah masker oksigen

"Kecepatan, Keamanan, dan Kenyamanan adalah Komitmen Kami"

Hubungi Kami

Jam Kerja

© 2024 Created by Webcare Indonesia