Snippets

Tim Schuitemaker Reservation Slice components

Created by Tim Schuitemaker last modified
global with sharing class Ctrl_Reservations {

	@AuraEnabled
    public static void createSlices(String recordId, String slices){
        try {
            String soql = Ctrl_Reservations.getCreatableFieldsSOQL('B25__Reservation__c','id=\''+recordId+'\'');
            B25__Reservation__c reservation = (B25__Reservation__c)Database.query(soql);

            List<Object> m = (List<Object>)JSON.deserializeUntyped(slices);
            List<B25__Reservation__c> reservations = new List<B25__Reservation__c>();

            for(Object o : m){
                Map<String, Object> item = (Map<String, Object>)o;

                B25__Reservation__c newRes = reservation.clone(false, false, false, false);

                String startStr = (String)item.get('sliceStart');
                String endStr = (String)item.get('sliceEnd');

                newRes.B25__StartLocal__c = null;
                newRes.B25__EndLocal__c = null;
                newRes.B25__Start__c = DateTime.valueOfGMT(startStr.substringBefore('.').replace('T', ' '));
                newRes.B25__End__c = DateTime.valueOfGMT(endStr.substringBefore('.').replace('T', ' '));
                newRes.Parent_Reservation__c = reservation.Id;
                reservations.add(newRes);
            }

            Database.insert(reservations);
        } catch (Exception e) {
            throw new AuraHandledException(e.getMessage());
        }
    }

	// Returns a dynamic SOQL statement for the whole object, includes only creatable fields since we will be inserting a cloned result of this query
    public static string getCreatableFieldsSOQL(String objectName, String whereClause){
         
        String selects = '';
         
        if (whereClause == null || whereClause == ''){ return null; }
         
        // Get a map of field name and field token
        Map<String, Schema.SObjectField> fMap = Schema.getGlobalDescribe().get(objectName.toLowerCase()).getDescribe().Fields.getMap();
        List<String> selectFields = new List<String>();
         
        if (fMap != null){
            for (Schema.SObjectField ft : fMap.values()){ // loop through all field tokens (ft)
                Schema.DescribeFieldResult fd = ft.getDescribe(); // describe each field (fd)
                if (fd.isCreateable()){ // field is creatable
                    selectFields.add(fd.getName());
                }
            }
        }
         
        if (!selectFields.isEmpty()){
            selects = String.join(selectFields, ',');        
        }
         
        return 'SELECT ' + selects + ' FROM ' + objectName + ' WHERE ' + whereClause;
         
    }

}
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
    <fullName>Duration_in_Minutes__c</fullName>
    <externalId>false</externalId>
    <formula>(B25__EndLocal__c - B25__StartLocal__c) *24 * 60</formula>
    <formulaTreatBlanksAs>BlankAsZero</formulaTreatBlanksAs>
    <label>Duration in Minutes</label>
    <precision>18</precision>
    <required>false</required>
    <scale>0</scale>
    <trackHistory>false</trackHistory>
    <trackTrending>false</trackTrending>
    <type>Number</type>
    <unique>false</unique>
</CustomField>
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
    <fullName>Parent_Reservation__c</fullName>
    <deleteConstraint>SetNull</deleteConstraint>
    <externalId>false</externalId>
    <label>Parent Reservation</label>
    <referenceTo>B25__Reservation__c</referenceTo>
    <relationshipLabel>Reservations</relationshipLabel>
    <relationshipName>Reservations</relationshipName>
    <required>false</required>
    <trackHistory>false</trackHistory>
    <trackTrending>false</trackTrending>
    <type>Lookup</type>
</CustomField>
@IsTest
private class Test_Slices {
        @IsTest private static void testCreateSlices(){
                DateTime d = DateTime.now();
                B25__Reservation__c reservation = new B25__Reservation__c();
                reservation.B25__Start__c = d;
                reservation.B25__End__c = d.addHours(1);
                Database.insert(reservation);

                Ctrl_Reservations.createSlices(reservation.Id, '[{"sliceStart":"2021-03-25T08:00:00.000Z","sliceEnd":"2021-03-25T08:30:00.000Z"},{"sliceStart":"2021-03-25T08:30:00.000Z","sliceEnd":"2021-03-25T09:00:00.000Z"},{"sliceStart":"2021-03-25T09:00:00.000Z","sliceEnd":"2021-03-25T09:30:00.000Z"},{"sliceStart":"2021-03-25T09:30:00.000Z","sliceEnd":"2021-03-25T10:00:00.000Z"},{"sliceStart":"2021-03-25T10:00:00.000Z","sliceEnd":"2021-03-25T10:30:00.000Z"},{"sliceStart":"2021-03-25T10:30:00.000Z","sliceEnd":"2021-03-25T11:00:00.000Z"},{"sliceStart":"2021-03-25T11:00:00.000Z","sliceEnd":"2021-03-25T11:30:00.000Z"},{"sliceStart":"2021-03-25T11:30:00.000Z","sliceEnd":"2021-03-25T12:00:00.000Z"},{"sliceStart":"2021-03-25T12:00:00.000Z","sliceEnd":"2021-03-25T12:30:00.000Z"},{"sliceStart":"2021-03-25T12:30:00.000Z","sliceEnd":"2021-03-25T13:00:00.000Z"},{"sliceStart":"2021-03-25T13:00:00.000Z","sliceEnd":"2021-03-25T13:30:00.000Z"},{"sliceStart":"2021-03-25T13:30:00.000Z","sliceEnd":"2021-03-25T14:00:00.000Z"},{"sliceStart":"2021-03-25T14:00:00.000Z","sliceEnd":"2021-03-25T14:30:00.000Z"},{"sliceStart":"2021-03-25T14:30:00.000Z","sliceEnd":"2021-03-25T15:00:00.000Z"},{"sliceStart":"2021-03-25T15:00:00.000Z","sliceEnd":"2021-03-25T15:30:00.000Z"},{"sliceStart":"2021-03-25T15:30:00.000Z","sliceEnd":"2021-03-25T16:00:00.000Z"},{"sliceStart":"2021-03-25T16:00:00.000Z","sliceEnd":"2021-03-25T16:30:00.000Z"},{"sliceStart":"2021-03-25T16:30:00.000Z","sliceEnd":"2021-03-25T17:00:00.000Z"},{"sliceStart":"2021-03-25T17:00:00.000Z","sliceEnd":"2021-03-25T17:30:00.000Z"},{"sliceStart":"2021-03-25T17:30:00.000Z","sliceEnd":"2021-03-25T18:00:00.000Z"}]');
                System.AssertEquals(21, [SELECT ID FROM B25__Reservation__c].size());
          }
}
<!-- Confirmation Dialog -->
<template>
    <lightning-card if:true={visible}>
        <div class="slds-container_small">
            <section role="dialog" tabindex="-1" aria-labelledby="modal-heading-01" aria-modal="true" aria-describedby="modal-content-id-1" class="slds-modal slds-fade-in-open">
                <div class="slds-modal__container">
                    <header class="slds-modal__header">
                        <h2 id="modal-heading-01" class="slds-text-heading_medium slds-hyphenate">{title}</h2>
                    </header>
                    <div class="slds-modal__content slds-p-around_medium" id="modal-content-id-1">
                        <p>{message}</p>
                    </div>
                    <footer class="slds-modal__footer">
                        <lightning-button variant="neutral"
                                          name="cancel"
                                          label={cancelLabel}
                                          title={cancelLabel}
                                          onclick={handleClick} 
                                          class="slds-m-left_x-small" ></lightning-button>
                        <lightning-button variant="brand"
                                          name="confirm"
                                          label={confirmLabel}
                                          title={confirmLabel}
                                          onclick={handleClick} 
                                          class="slds-m-left_x-small" ></lightning-button>
                    </footer>
                </div>
            </section>
            <div class="slds-backdrop slds-backdrop_open"></div>
        </div>
    </lightning-card>
</template>
//https://github.com/marcoalmodova/confirm-dialog
import {LightningElement, api} from 'lwc';

export default class ConfirmationDialog extends LightningElement {
    @api visible; //used to hide/show dialog
    @api title; //modal title
    @api name; //reference name of the component
    @api message; //modal message
    @api confirmLabel; //confirm button label
    @api cancelLabel; //cancel button label
    @api originalMessage; //any event/message/detail to be published back to the parent component

    //handles button clicks
    handleClick(event){
        //creates object which will be published to the parent component
        let finalEvent = {
            originalMessage: this.originalMessage,
            status: event.target.name
        };

        //dispatch a 'click' event so the parent component can handle it
        this.dispatchEvent(new CustomEvent('click', {detail: finalEvent}));
    }
}
1
2
3
4
5
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>50.0</apiVersion>
    <isExposed>false</isExposed>
</LightningComponentBundle>
<template>
    <lightning-card  title="Slice reservation"  icon-name="utility:cut">
        <div class="slds-p-around_medium">
            <lightning-input type="text" label="Enter a slicing scheme" required onchange={handleDividerChange}></lightning-input>
        </div>
        <template if:true={nrOfSlices}>
            <div class="slds-p-around_medium">
                <h3 class="slds-text-heading_small">We will create <strong>{nrOfSlices}</strong> slices</h3>
                <ul>
                    <template for:each={slices} for:item="slice">
                        <li key={slice.key}>
                            <lightning-formatted-date-time value={slice.sliceStart} hour="2-digit" minute="2-digit" time-zone="Europe/London"></lightning-formatted-date-time> - 
                            <lightning-formatted-date-time value={slice.sliceEnd} hour="2-digit" minute="2-digit" time-zone="Europe/London"></lightning-formatted-date-time> 
                        </li>
                    </template>
                </ul>
            </div>
            <div class="slds-p-around_medium">
                <lightning-button variant="brand" label="Create Slices" title="Create Slices" onclick={handleClick}></lightning-button>
            </div>
        </template>
    </lightning-card>

    <c-confirm-dialog 
        title='Slicing Complete?'
        message='Are you sure you want to create the slices?'
        confirm-label='Yes, I am sure'
        cancel-label='Cancel'
        visible={isDialogVisible}
        original-message={originalMessage}
        name="confirmModal"
        onclick={handleConfirmClick}>
    </c-confirm-dialog>

</template>
import { LightningElement, api, track, wire } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import { getRecord, getFieldValue } from 'lightning/uiRecordApi';
import createSlices from '@salesforce/apex/Ctrl_Reservations.createSlices';

const FIELDS = ['B25__Reservation__c.B25__Start__c', 'B25__Reservation__c.B25__End__c', 'B25__Reservation__c.Duration_in_Minutes__c'];


export default class SliceReservation extends LightningElement {
    @api recordId;
    reservation;
    start;
    errors;
    duration;
    nrOfSlices;
    @track slices = [];
    @track isDialogVisible = false;

    @wire(getRecord, { recordId: '$recordId', fields: FIELDS })
    wiredRecord({ error, data }) {
        if (error) {
            let message = 'Unknown error';
            if (Array.isArray(error.body)) {
                message = error.body.map(e => e.message).join(', ');
            } else if (typeof error.body.message === 'string') {
                message = error.body.message;
            }
            this.dispatchEvent(
                new ShowToastEvent({
                    title: 'Error loading reservation',
                    message,
                    variant: 'error',
                }),
            );
        } else if (data) {
            this.reservation = data;
            this.start = this.reservation.fields.B25__Start__c.value;
            this.end = this.reservation.fields.B25__End__c.value;
            this.duration = this.reservation.fields.Duration_in_Minutes__c.value;
        }
    }

    handleDividerChange(event) {
        var divider = event.target.value;
        divider = divider.trim();
        if(divider == null || divider == ''){
            this.nrOfSlices = null;
            this.slices = []; 
            return;
        }

        var dividers = divider.split(' ');
        //simple slice in even parts
        if(dividers.length == 1) {
            this.nrOfSlices = Math.ceil(this.duration / divider);
            this.addSimpleSlices(divider);
            return;
        }

        var sliceLength = 0;
        dividers.forEach(function(entry) {
            sliceLength += parseInt(entry);
        });

        //exactly divided into the available minutes
        if(sliceLength == this.duration){
            console.log('exactly divided');
            this.nrOfSlices = dividers.length;
            this.addSlices(dividers);
            return;
        // else we need to add one more slice at the end
        } else if(sliceLength < this.duration){
            console.log('needs slide at end');
            
            this.nrOfSlices = dividers.length + 1;
            this.addSlices(dividers);

            //add final slices of remaining minutes
            var endItem = new Date(this.end);
            var slice = {
                sliceStart: this.addMinutes(endItem, (this.duration - sliceLength)*-1),
                sliceEnd: endItem
            };
            this.slices.push(slice);
            return;
        }   
    }

    addMinutes(date, minutes) {
        return new Date(date.getTime() + parseInt(minutes)*60000);
    }

    addSimpleSlices(divider) {
        var startItem = new Date(this.start);
        this.slices = [];

        var sliceLength = 0;
        for (var i = 0; i < Math.floor(this.duration / divider); i++) {
            var slice = {
                sliceStart: startItem,
                sliceEnd: this.addMinutes(startItem, divider)
            };
            sliceLength += parseInt(divider);
            startItem = slice.sliceEnd;
            this.slices.push(slice);
        }

        if(sliceLength < this.duration){
            var slice = {
                sliceStart: startItem,
                sliceEnd: this.addMinutes(startItem, (this.duration - sliceLength))
            };
            this.slices.push(slice);
        }
    }

    addSlices(dividers){
        var startItem = new Date(this.start);
        this.slices = [];
        
        for (var i = 0; i < dividers.length; i++) {
            var slice = {
                sliceStart: startItem,
                sliceEnd: this.addMinutes(startItem, dividers[i])
            };
            startItem = slice.sliceEnd;
            this.slices.push(slice);
        }
    }

    handleClick(event) {
        this.isDialogVisible = true;
    }

    handleConfirmClick(event) {
        if(event.detail !== 1 && event.detail.status === 'confirm') {
            createSlices({ recordId: this.recordId, slices: JSON.stringify(this.slices) })
                .then((result) => {
                    this.dispatchEvent(
                        new ShowToastEvent({
                            title: 'Success',
                            message: 'We created the child reservations',
                            variant: 'success',
                        }),
                    );
                    this.errors = undefined;
                    this.slices = [];
                    this.nrOfSlices = null;
                })
                .catch((error) => {
                    this.handleError(error);
                    this.reservations = undefined;
                });
        }
        this.isDialogVisible = false;
        
    }

    handleError(error) {
        console.log(error);
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>51.0</apiVersion>
    <isExposed>true</isExposed>
    <masterLabel>Slice Reservations</masterLabel>
    <description>Allows you to 'slice' reservation into smaller pieces.</description>
    <targets>
        <target>lightning__RecordPage</target>
    </targets>
    <targetConfigs>
        <targetConfig targets="lightning__RecordPage">
            <objects>
                <object>B25__Reservation__c</object>
            </objects>
        </targetConfig>
    </targetConfigs>
</LightningComponentBundle>

Comments (0)

HTTPS SSH

You can clone a snippet to your computer for local editing. Learn more.