michielfb/laravel-nova-time-field
A Laravel Nova field that adds a time picker input for selecting times in Nova resources, with simple formatting and seamless integration into Nova forms and indexes.
Architecture fit. The package is a presentation-layer Nova field: one Field subclass, one Vue component, a service provider. It touches nothing in the domain model — no migrations, no publish step, no repository or service bindings. That means it can be dropped into an existing Nova resource and removed again without touching business logic, which is the property you want from a component this small. Its only meaningful architectural claim is semantic: it asserts that an attribute is a wall-clock time-of-day, not an instant. That assertion is the actual decision being made; the widget is downstream of it.
Integration feasibility. Low. composer require after Nova credentials are configured, auto-discovered provider, drop the field into a resource. The one non-obvious piece is the wrapper subclass in app/Nova/Fields, which costs ~30 lines and buys a single seam for conventions, validation rules, and any future fork. Realistic envelope: one engineer-day including round-trip verification against the database, more if the target column is currently DATETIME.
Technical risk. Concentrated in stewardship, not runtime:
laravel/nova constraint.dist/js is not committed you inherit an npm build step for a twenty-star dependency — a worse trade than writing the component. If it is committed, a Nova upgrade can silently invalidate it and the field renders blank with no PHP error.TIME column holding 09:30:15 returns 09:30:00 after one edit-and-save, with no error and no log line.NULL. Browsers submit "" for a cleared input; strict MySQL rejects that on a time column rather than storing NULL.Key questions to answer before committing:
composer.json name it explicitly?dist/js/field.js committed, and does it load cleanly in our Nova version?00:00, 23:59, and a cleared value — verified via DB::table(), not the browser?TIME, or is it DATETIME with a throwaway date?Text field with a regex?Stack fit. Addressable only if Laravel Nova is already in the estate — that is the first filter, not the last. Within Nova, the fit is clean: no competing field conventions to reconcile, no service container involvement, no config surface. If an internal field kit already exists, adopting a second convention costs more in coordination than writing a local field costs in effort, and the recommendation flips to build.
Migration path. The package changes the input widget; it does not change storage. Keep those separate:
HH:MM into the existing column, read it back through a format accessor, verify round-trip on real rows including a legacy row with non-zero seconds.TIME once the write shape is proven.Never combine "change the widget" and "change the storage type" in one release — if the round-trip is wrong you will not know which half broke it. Add a model mutator or saving hook to converge on a single storage shape (null normalization, seconds policy) so the rest of the app stays honest regardless of what the browser sent.
Range validation is yours, not the field's. Note that Laravel's after: rule on bare times rejects legitimate cross-midnight windows (22:00–02:00); compare minutes-since-midnight explicitly for overnight ranges.
Compatibility. Pin the version explicitly — do not float on ^1.0. Read the declared laravel/nova constraint against the major we run, confirm dist/js is present, and check resolve/fill behavior on empty input — all readable in one sitting before shipping. Resolve Nova credentials in CI on their own merits; that is an onboarding blocker that will masquerade as a package problem.
Sequencing. This is a cheap probe, and it should be sequenced as one:
App\Nova\Fields\TimeOfDay with house rules and a format hint.opens_at, shifts.starts_at).Do not run this concurrently with, or immediately before, a Nova major upgrade. Adopting compatibility risk at the moment it is most likely to bite is a sequencing error.
Maintenance. Runtime cost is nil; stewardship cost is real. Add the dependency to the inventory with a named maintainer and a written exit plan. The fork is hours, not weeks — roughly a hundred lines — and the wrapper class makes the swap a one-line namespace edit. Treat "we will fork if needed" as a plan, not a hope, and name the trigger (upstream quiet through the next Nova major, or a needed feature like seconds/step support).
Support. There is no vendor, no SLA, and no release cadence commitment. You are adopting code, not buying support. Internal support burden is low but non-zero: the failure modes below will be diagnosed by whoever owns the admin surface, and the debugging sequence should be documented rather than rediscovered — (1) does the label render, (2) are assets 404ing, (3) what is the actual form payload, (4) what is in the column. Each step isolates a different layer; guessing at the Vue side first wastes the afternoon.
Scaling. Runtime scales with Nova itself; the field adds nothing measurable. The scaling risk is organizational: once two or three resources need time entry, divergent implementations become a data-quality and support problem. Standardize on the wrapper class early and refuse the "text field with a regex, just this once" drift — that drift is precisely what this component exists to prevent.
Failure modes to watch:
| Failure | Symptom | Detection |
|---|---|---|
| Stale/missing compiled assets | Form renders, input missing or unstyled | Browser console 404; mix-manifest.json mismatch |
| Nova major incompatibility | Field blank in production, no PHP error | Pre-upgrade staging pass |
| Seconds truncation | Legacy 09:30:15 becomes 09:30:00 |
Compare raw column before/after edit |
| Empty vs null | MySQL incorrect-value error on clear | Explicit cleared-field test |
| Locale rendering | 12h control writing into a 24h column | Operator browser matrix smoke test |
| Cross-midnight range rejected | Valid overnight window fails validation | Minutes-since-midnight rule + test |
Ramp-up. A Nova-fluent engineer is productive in under a day; the source is small enough to read fully and the wrapper pattern is self-documenting. The larger ramp-up is operator-facing, not developer-facing: put the expected format in ->help() ("24-hour, e.g. 09:30"), treat server-side date_format:H:i as non-negotiable rather than belt-and-braces, and smoke-test on the actual browsers operations uses. Budget the time saved on integration into the things the field does not do — server-side validation, round-trip testing against existing rows, and a decision on the browser matrix. That is where the residual risk actually lives.
How can I help you explore Laravel packages today?