Here is a very basic and crude way to achive what you need.
I sometimes use this myself for quick validations, helps the client not miss anything important.
I created the same custom post type slug so the css/js targets would be as similar to yours.
Once you get the concept, change this how ever you need. Move css and js code to proper files, add relevant comments, you have alot of room to do what you need.
add_action('admin_footer', 'bt_before_submit_validation');
function bt_before_submit_validation () {
?>
<style>
.post-type-appointments .custom-error {
padding: 10px;
box-sizing: border-box;
display: block;
}
.post-type-appointments #publishing-action {
position: relative;
width: 100%;
}
.post-type-appointments #publishing-action .vbs-btn {
width: 100%;
height: 100%;
position: absolute;
z-index: 1;
top: 0;
left: 0;
}
</style>
<script>
($ => {
// add facade submit button
// This button will be used to trigger validation before submit
// (change "Validate and Publish") to what ever you want
$('.post-type-appointments #publishing-action [type="submit"]').before('<button type="button" class="vbs-btn button button-primary">Validate and Publish</button>');
// now that we have a button we can use it to start validating
$(document).on('click', '.post-type-appointments #publishing-action .vbs-btn', e => {
// we will do a basic validation for title
const $this = $(e.currentTarget);
// remove all previous errors
$('.custom-error').remove();
// will indicate if validation was successful
// by default we assume everything is good, once we encounter a problem we will change this to false
let isValid = true;
// get the elements
const $title = $('[name="post_title"]');
// get the value
const titleVal = $title.val().trim();
// validate title field
if (!titleVal) {
isValid = false;
$title.after('<span class="custom-error notice notice-error">Title field cannot be empty</span>');
} else if (titleVal.length < 10) {
isValid = false;
$title.after('<span class="custom-error notice notice-error">Title field must contain at least 10 characters.</span>');
} // continue adding as many validations as needed
// validation faild, return
if (!isValid) return;
// everything is ok, lets continue
// trigger click on submit button
$this.next('[type="submit"]').click();
// remove the facade button, this is optional
$this.remove();
});
})(jQuery);
</script>
<?php
}
If something doesn't work, make sure to check the css/js targets.
Everything was tested on a new wordpress install with the cptui plugin in order to create the appointments
post type