tomhatzer/nova-business-hours
A Laravel Nova field for managing weekly business hours on Eloquent models. Set opening and closing times per weekday through a clean Nova form interface, store schedules as JSON, and display them alongside your resources.
This package is an early-stage (v0.1.x) Laravel field for editing opening-hours data, designed to pair with spatie/opening-hours. The v0.1.1 release is documentation-only: it adds a README snippet showing how to hand the field's value to spatie/opening-hours. There are no code, API, or config changes to migrate for.
Start here:
composer require the package alongside spatie/opening-hours in the same project — the integration snippet assumes both are installed.OpeningHours instance).json (or text) column to the model that owns the hours, e.g. opening_hours.Spatie\OpeningHours\OpeningHours in the model so the rest of your app (controllers, Blade, API resources) never touches the raw JSON.First use case: a "business hours" editor on a Location/Venue resource, where an admin sets per-weekday ranges and the front end renders "Open now / Closed" via OpeningHours::isOpen().
Store raw, hydrate late. Keep the DB column as the field's native serialised shape (an array of day → ranges). Do the OpeningHours conversion in an accessor or a small cast so the field round-trips cleanly and spatie/opening-hours remains a presentation/query concern:
protected $casts = ['opening_hours' => 'array'];
public function openingHours(): OpeningHours
{
return OpeningHours::create($this->opening_hours ?? []);
}
Seed sensible defaults. An empty field value is common on first create. Provide a default week (e.g. Mon–Fri 09:00–17:00, weekend closed) either in the field's default()/resolveUsing or via a model observer, so OpeningHours::create() never receives a malformed array.
Use it as the single source of truth. Once the field owns the data, derive everything else — isOpen(), nextOpen(), "closed on public holidays" exceptions, and JSON API output — from the hydrated object rather than re-parsing the column in multiple places. This keeps the field's format an internal detail you can change on a minor bump.
Validation at the edge. Pair the field with backend validation (start < end, no overlapping ranges per day) rather than trusting the UI component. Early 0.x field packages rarely ship a full validation rule.
Testing. Feature-test the resource form with a fixture array and assert the persisted column matches; unit-test the hydration helper separately. Because spatie/opening-hours parses on construction, a test that builds OpeningHours::create() from your stored fixtures catches format drift quickly after any package upgrade.
"vendor/package": "0.1.*" (or an exact tag) and read every changelog. The v0.1.1 note itself is a signal that documentation is still being filled in — expect API surfaces to be lightly documented.spatie/opening-hours snippet. Treat the source (field class + Vue component) as the real spec for accepted value shapes before wiring it into production forms.spatie/opening-hours evaluates against a timezone you must supply; store the venue timezone separately and pass it explicitly, otherwise "open now" drifts for remote locations and around DST boundaries.spatie/opening-hours exceptions. Test these explicitly rather than assuming the field enforces them.json on MySQL 5.7+/Postgres; on older MySQL fall back to text and cast. Empty string vs null vs [] will each behave differently on hydration — normalise in the accessor.$casts, the resource field name matches the column, and the stored array keys are the day names spatie/opening-hours expects. Log the hydrated object in a Tinker session before blaming the Vue component.resolve/fill methods is the practical way to add custom formatting (24h vs 12h, locale day labels). Prefer subclassing the field over editing vendor files so upgrades stay painless.spatie/opening-hours API; if you are on an older major of that package, adapt the snippet — the field release does not pin or constrain it for you.How can I help you explore Laravel packages today?