This file contains lessons learned, architectural decisions, and specific workarounds found during development. AGENTS: Read this before starting any task to avoid repeating past mistakes.
- Context: While removing 'cost_price' from the direct product form, the user requested a future feature related to validating selling price against purchase item costs.
- Requirement: Add a "text alert" if
harga_jual * qty > purchase_items. This implies a need to check if the total selling value (selling price multiplied by quantity) of derived products exceeds the original cost recorded in aPurchaseItem. - Note: This feature needs to be implemented on a form or view that allows for input of quantity and displays selling price in relation to available purchase items and their costs.
- Context: Standard
shadcn-vuecomponents were implemented usingradix-vue, which broke existing components (Button) and caused errors in new ones (Calendar). This was based on a misunderstanding of the project's setup. - Problem Analysis: This project uses a customized
shadcn-vuesetup that is built uponreka-ui, not the more commonradix-vue. Replacing components withradix-vueversions is a breaking change. Components must be used according to thereka-uiAPI. - Solution:
- All core components (
Button.vue,Calendar.vue, etc.) were reverted to their originalreka-uiimplementations. - The
Calendarcomponent error (defaultValue.copy is not a function) was fixed by providing it the expected date object. This involves importingtodayandgetLocalTimeZonefrom@internationalized/date, initializing the formv-modelwithtoday(getLocalTimeZone()), and transforming the value back to a string (.toString()) on form submission.
- All core components (
- Rule: DO NOT replace
reka-uibased components withradix-vueversions. This project'sshadcn-vueimplementation is customized. When working with components likeCalendar, use date objects from@internationalized/dateto ensure compatibility.
- Context: A persistent bug caused
@tanstack/vue-tableto fail to refresh its data visually after filtering (usingrouter.getwithpreserveState: true) or after data-mutating actions (router.delete,router.post) after a hard page refresh, despitepropsupdates andtable.reset()calls. This issue did not occur during hot-reloading (HMR). - Problem Analysis:
- Inertia's
preserveState: true(used for filtering to maintain input focus) prevents component re-mounts. This, combined with the build process, somehow led to a subtle reactivity detachment in the@tanstack/vue-tableinstance. - Even forcing new array references (
[...newData]) for the table data did not resolve the issue, indicating thattable.reset()or the table's internal reactivity mechanism was failing to re-render in this specificpreserveStatecontext after a hard refresh. preserveState: trueis the default forPOST,PUT,PATCH,DELETErequests in Inertia, which also caused issues for data-mutating actions until explicitly set tofalse.
- Inertia's
- Solution(s):
- For Data-Mutating Actions (
DELETE,POST,PUT,PATCH): Explicitly setpreserveState: falsein theroutercall options (e.g.,router.delete(..., { preserveState: false })). This forces a full component re-render and re-initialization, reliably updating the table. - For
preserveState: trueVisits (e.g., Filtering): WhenpreserveState: trueis required (e.g., for search inputs to maintain focus), and the table fails to refresh:- Implement the
v-ifre-rendering trick:- Create a
ref(e.g.,isTableVisible = ref(true)). - Wrap the entire table component in the template with
v-if="isTableVisible". - In a
watcher that detects prop changes, setisTableVisible.value = false, useawait nextTick(), then setisTableVisible.value = true. This forces Vue to destroy and re-mount the table component from scratch, ensuring it initializes with the latest data.
- Create a
- Implement the
- For Data-Mutating Actions (
- Rule: When encountering persistent table refresh issues with
@tanstack/vue-tableand Inertia, particularly withpreserveState: trueafter a hard refresh:- Explicitly manage
preserveStateforroutercalls:falsefor actions requiring full re-render,truefor state-preserving actions like filtering. - If
preserveState: truecauses issues, implement thev-ifre-rendering trick to force a full re-mount of the table component.
- Explicitly manage
- Context: A new module was created with an index page that was stylistically and functionally inconsistent with the rest of the admin panel. Several iterations were required to align it.
- Solution: The
PurchaseItemsmodule was refactored to match the conventions found inProducts/Index.vue. This established a clear standard for all admin CRUD interfaces. - Rules:
- Layout: All admin pages must use
AuthenticatedLayout.vue. The page title must be placed in the#headerslot. The main content must be wrapped in a<div class="tw-bg-white tw-rounded-md tw-p-4">. - Data Tables: All data tables must be implemented using
@tanstack/vue-table. Columns should be defined programmatically in the<script>section. The table itself should be rendered using<FlexRender>. - Actions: Row actions must be placed in a dropdown menu. For maintainability, this dropdown should be a dedicated component (e.g.,
DropdownAction.vue) and rendered in the table cell using Vue'sh()function. - Pagination: Use simple "Previous" and "Next" buttons with links provided by Inertia's paginator, not a full list of page numbers.
- Styling: Follow all project-specific styling rules, including the
tw-prefix for Tailwind classes and using component props (e.g.,size="icon") over manual styling where possible. - Localization: All user-facing text in UI components, including labels, buttons, placeholders, and common navigation terms, MUST be in Indonesian.
- Layout: All admin pages must use
- Context: Implementing soft delete functionality for a model involves changes across the database, model, controller, routes, and frontend UI.
- Solution:
- A migration was created to add a
deleted_atcolumn to thepurchase_itemstable. - The
PurchaseItemmodel was updated to use theSoftDeletestrait. - The
PurchaseItemControllerwas updated to:- Modify the
indexmethod to filter for active, trashed, or all items based on atrashedquery parameter. - Change resource methods (
show,edit,update,destroy) to usewithTrashed()->findOrFail($id)for consistent handling of soft-deleted items. - Add
restoreandforceDeletemethods.
- Modify the
- New routes were added for
restore(POST) andforceDelete(DELETE). - The
Index.vuepage was updated with a filter (dropdown) to view trashed items. - The
DropdownAction.vuecomponent was updated to conditionally show "Restore" and "Force Delete" actions for soft-deleted items.
- A migration was created to add a
- Rule: When implementing soft deletes for a model, ensure all layers (database, model, controller, routes, and frontend) are updated to correctly support the lifecycle of soft-deleted items, including filtering, restoring, and permanent deletion.
- Context: In PostgreSQL, the
LIKEoperator is case-sensitive, leading to incomplete search results when users expect case-insensitive matching. - Solution: For case-insensitive string searches on PostgreSQL, use the
ILIKEoperator instead ofLIKEin SQL queries. - Rule: When implementing case-insensitive string searches in Laravel for PostgreSQL databases, use
where('column', 'ilike', '%search%').
- Context: A dynamic search query was attempting to compare a
bigintcolumn (id) with a non-numeric string, resulting in ainvalid input syntax for type bigintSQL error. - Solution: Before adding an
orWhereclause for a numeric column in a dynamic search, explicitly check if the search term is numeric usingis_numeric($search). - Rule: Always validate the type of dynamic search parameters against the expected column type (e.g.,
is_numeric()for integer/bigint columns) to prevent SQL syntax errors.
- Context: After performing actions (delete, restore), the
@tanstack/vue-tablewas not automatically updating despite the underlying data prop changing via Inertia. - Solution: While
tableDatais a computed property, directly watchingprops.purchaseItemswithdeep: trueand callingtable.reset()on change proved to be the most reliable way to trigger a re-render of the@tanstack/vue-tablewhen data changes. - Rule: For
@tanstack/vue-tableimplementations with Inertia, usewatch(() => props.dataProperty, () => table.reset(), { deep: true })to ensure reliable table updates when the Inertia prop changes.
- Context: When creating new Admin Vue pages, the initial assumption was to use
AdminLayout.vuefor the layout component. This led to a build error as no such component existed. - Solution: Examination of existing Admin pages (e.g.,
Admin/Dashboard.vue) revealed thatAuthenticatedLayout.vueis the correct component to use for Admin pages, imported from@/Layouts/AuthenticatedLayout.vue. - Rule: For new Admin Vue pages, always use
AuthenticatedLayout.vueas the main layout component.
- Context: When importing Shadcn UI components in Vue files, the path alias
@/components/ui/...was used, leading to a build error. - Solution: Investigation of the project structure revealed that Shadcn UI components are located under
resources/js/components-shadcn/ui/. The correct import path uses@/components-shadcn/ui/.... - Rule: When importing Shadcn UI components, use the path alias
@/components-shadcn/ui/.
- Context: Vue components were created using standard Tailwind CSS utility classes (e.g.,
flex,items-center) which was incorrect. The initial correction to-tw-was also wrong. - Solution: The project convention requires all Tailwind CSS classes to be prefixed with
tw-(e.g.,tw-flex,tw-items-center). All newly created Vue components were updated to follow this rule. - Rule: When writing or editing Vue components, all Tailwind CSS classes MUST be prefixed with
tw-.
- Context: When using Shadcn UI's
Buttoncomponent, explicitly applying Tailwind CSS classes liketw-w-8 tw-h-8 tw-p-0can interfere with the component's internal styling, especially for icon-only buttons. This caused content (like theMoreHorizontalicon) to be clipped or not rendered correctly. - Solution: For icon-only buttons, leverage the
size="icon"prop provided by the Shadcn UIButtoncomponent. This prop automatically applies the correct width, height, and padding for icon buttons, ensuring content is displayed properly without manual class overrides. - Rule: When styling Shadcn UI
Buttoncomponents, especially for icon buttons, prefer using thesizeprop (e.g.,size="icon") over explicittw-w-*,tw-h-*, andtw-p-*classes to avoid conflicts and ensure proper rendering.
- Context: There was confusion about whether certain common English terms ("Filter", "Next", "Previous") should be localized. The initial implementation was fully Indonesian.
- Solution: The final decision is to localize all user-facing text to Indonesian, including common terms and placeholders, for consistency across the application. The components have been updated to reflect this.
- Rule: All user-facing text in UI components, including labels, buttons, placeholders, and common navigation terms, MUST be in Indonesian.