/home/edulekha/crm.edulekha.com/resources/js/components/filters/AppFiltersDateRule.vue
<template>
  <div ref="wrapperRef">
    <div class="tw-flex" v-if="isBetweenOperator">
      <div class="tw-flex">
        <div
          class="checkbox"
          data-toggle="tooltip"
          :data-title="$t('filter_use_dynamic_dates')"
        >
          <input
            type="checkbox"
            :id="`${dateRuleId}betweenDynamic`"
            v-model.bool="isDynamicBetweenOperator"
          />
          <label :for="`${dateRuleId}betweenDynamic`"></label>
        </div>
        <div class="date">
          <input
            v-show="!isDynamicBetweenOperator"
            type="text"
            :id="dateRuleIdStart"
            autocomplete="off"
            class="form-control datepicker"
          />
          <input
            type="text"
            v-if="isDynamicBetweenOperator"
            v-model="localValue.start"
            @input="validateDynamicBetweenDate($event, 'start')"
            placeholder="this friday"
            autocomplete="off"
            :class="[
              'form-control',
              { '!tw-border-danger-500': !dynamicBetweenDateIsValid.start },
            ]"
          />
        </div>
      </div>
      <div class="tw-ml-1">
        <input
          v-show="!isDynamicBetweenOperator"
          type="text"
          autocomplete="off"
          :id="dateRuleIdEnd"
          class="form-control datepicker"
        />
        <input
          type="text"
          v-if="isDynamicBetweenOperator"
          v-model="localValue.end"
          @input="validateDynamicBetweenDate($event, 'end')"
          placeholder="next friday"
          autocomplete="off"
          :class="[
            'form-control',
            { '!tw-border-danger-500': !dynamicBetweenDateIsValid.end },
          ]"
        />
      </div>
      <p
        class="tw-text-xs tw-top-[37px] tw-ml-7 tw-absolute"
        v-if="isDynamicBetweenOperator"
      >
        Based on PHP
        <a
          href="https://www.php.net/manual/en/function.strtotime.php"
          target="_blank"
        >
          "strtotime"
        </a>
        function.
      </p>
    </div>
    <div v-else-if="rule.operator === 'dynamic'">
      <input
        type="text"
        class="form-control"
        placeholder="this friday, first day of next month, etc..."
        :class="[
          'form-control',
          { '!tw-border-danger-500': !dynamicDateIsValid },
        ]"
        autocomplete="off"
        :id="dateRuleId"
        @input="validateDynamicDate"
      />
      <p class="tw-text-xs tw-top-[37px] tw-absolute">
        Based on PHP
        <a
          href="https://www.php.net/manual/en/function.strtotime.php"
          target="_blank"
        >
          "strtotime"
        </a>
        function. {{ dynamicDateValue !== "fails" ? dynamicDateValue : "" }}
      </p>
    </div>
    <div v-else>
      <input
        type="text"
        class="form-control datepicker"
        autocomplete="off"
        :id="dateRuleId"
      />
    </div>
  </div>
</template>
<script setup>
import {
  ref,
  watch,
  computed,
  onMounted,
  onBeforeUnmount,
} from "vue";

const props = defineProps({ rule: { type: Object, required: true } });
const emit = defineEmits([
  "update-rule-value",
  "has-errors",
  "has-dynamic-value",
]);

const localValue = ref(null);

const wrapperRef = ref(null);
const dateRuleId = `dateRule${props.rule.id}-${generatRuleId()}`;
const dateRuleIdStart = `dateRule${props.rule.id}-${generatRuleId()}-Start`;
const dateRuleIdEnd = `dateRule${props.rule.id}-${generatRuleId()}-End`;
const dateRuleSelector = `#${dateRuleId}`;
const dateRuleSelectorStart = `#${dateRuleIdStart}`;
const dateRuleSelectorEnd = `#${dateRuleIdEnd}`;
const dynamicDateIsValid = ref(true);
const dynamicBetweenDateIsValid = ref({ start: true, end: true });
const dynamicDateValue = ref("");

const isBetweenOperator = computed(
  () =>
    props.rule.operator === "between" || props.rule.operator === "not_between"
);

const isDynamicBetweenOperator = ref(
  isBetweenOperator.value && props.rule.has_dynamic_value
);

if (isDynamicBetweenOperator.value) {
  localValue.value = { start: "", end: "" };
}

function handlerDatePickerChange(e) {
  localValue.value = e.target.value;
}

function handlerDatePickerChange1(e) {
  if (!localValue.value) {
    localValue.value = { start: "", end: "" };
  }

  localValue.value.start = e.target.value;
}

function handlerDatePickerChange2(e) {
  if (!localValue.value) {
    localValue.value = { start: "", end: "" };
  }

  localValue.value.end = e.target.value;
}

function destroyDatePicker() {
  $(dateRuleSelector).off("change", handlerDatePickerChange);
  $(dateRuleSelectorStart).off("change", handlerDatePickerChange1);
  $(dateRuleSelectorEnd).off("change", handlerDatePickerChange2);
  $(dateRuleSelector).datetimepicker("destroy");
  $(dateRuleSelectorStart).datetimepicker("destroy");
  $(dateRuleSelectorEnd).datetimepicker("destroy");
}

function valueChangeHandler(value) {
  init_datepicker();

  if (isBetweenOperator.value) {
    localValue.value = { start: "", end: "" };

    const start = value ? value[0] : "";
    const end = value ? value[1] : "";

    localValue.value.start = start || "";
    localValue.value.end = end || "";

    $(dateRuleSelectorStart).val(localValue.value.start);
    $(dateRuleSelectorEnd).val(localValue.value.end);

    $(dateRuleSelectorStart).on("change", handlerDatePickerChange1);
    $(dateRuleSelectorEnd).on("change", handlerDatePickerChange2);
  } else {
    localValue.value = value || "";
    $(dateRuleSelector).val(localValue.value);

    $(dateRuleSelector).on("change", handlerDatePickerChange);
  }
}

watch(
  () => props.rule.operator,
  (newVal) => {
    isDynamicBetweenOperator.value = false;
    destroyDatePicker();
    valueChangeHandler("");
    emit("has-errors", false);
    emit("has-dynamic-value", newVal === "dynamic");
  },
  { flush: "post" }
);

watch(
  localValue,
  (newVal) => {
    emit(
      "update-rule-value",
      isBetweenOperator.value ? [newVal.start, newVal.end] : newVal
    );
  },
  { deep: true }
);

watch(
  isDynamicBetweenOperator,
  (newVal) => {
    if (newVal) {
      localValue.value.start = "";
      localValue.value.end = "";
      $(dateRuleSelectorStart).val("");
      $(dateRuleSelectorEnd).val("");
    } else {
      valueChangeHandler("");
    }
    emit("has-dynamic-value", newVal);
  },
  { flush: "post" }
);

const validateDynamicBetweenDate = debounce(function (e, type) {
  if (!e.target.value) {
    dynamicBetweenDateIsValid.value[type] = true;
    emit(
      "has-errors",
      !dynamicBetweenDateIsValid.value.start ||
        !dynamicBetweenDateIsValid.value.end
    );
    return;
  }

  $.post(`${admin_url}filters/validate_dynamic_date`, {
    value: e.target.value,
  }).done((response) => {
    dynamicBetweenDateIsValid.value[type] = response !== "fails";
    emit(
      "has-errors",
      !dynamicBetweenDateIsValid.value.start ||
        !dynamicBetweenDateIsValid.value.end
    );
  });
}, 500);

const validateDynamicDate = debounce(function (e) {
  if (!e.target.value) {
    dynamicDateIsValid.value = true;
    emit("has-errors", false);
    return;
  }

  $.post(`${admin_url}filters/validate_dynamic_date`, {
    value: e.target.value,
  }).done((response) => {
    dynamicDateIsValid.value = response !== "fails";
    dynamicDateValue.value = response;
    emit("has-errors", dynamicDateIsValid.value === false);
  });
}, 500);

onMounted(() => {
  valueChangeHandler(props.rule.formatted_value);
});

onBeforeUnmount(() => {
  emit("has-errors", false);
});

function generatRuleId(length = 5) {
  const characters =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

  let result = "";
  const charactersLength = characters.length;
  for (let i = 0; i < length; i++) {
    result += characters.charAt(Math.floor(Math.random() * charactersLength));
  }
  return result;
}

function debounce(fn, delay) {
  var timeoutID = null;
  return function () {
    clearTimeout(timeoutID);
    var args = arguments;
    var that = this;
    timeoutID = setTimeout(function () {
      fn.apply(that, args);
    }, delay);
  };
}
</script>