Skip to content

Pinia Task 6 - product-tables #6823

@n-lark

Description

@n-lark

Task 6 — product-tables (PR 7)

Gate: Phase 0 merged
Vuex module: frontend/src/store/modules/product/tables/index.js
New file: frontend/src/stores/product-tables.js
Persistence: databases, newTable → localStorage (cleared on logout)
Cross-store dependency: reads account.team.id for all API calls — use _account-bridge.js

6.1 — Create the Pinia store

// frontend/src/stores/product-tables.js
import { defineStore } from 'pinia'
import tablesApi from '../api/tables.js'
import { hashString } from '../composables/String.js'
import { useAccountBridge } from './_account-bridge.js'

const emptyColumn = { name: '', type: '', nullable: false, default: '', hasDefault: false, unsigned: false }

export const useProductTablesStore = defineStore('product-tables', {
    state: () => ({
        databases: {},
        databaseSelection: null,
        tables: {},
        tableSelection: null,
        newTable: { name: '', columns: [{ ...emptyColumn }] },
        isLoading: false
    }),
    getters: {
        database: (state) => state.databases[state.databaseSelection] ?? null,
        tables: (state) => (databaseId) => state.tables[databaseId] ?? [],
        selectedTable: (state) => {
            if (Object.keys(state.tables).includes(state.databaseSelection)) {
                return state.tables[state.databaseSelection]?.find(t => t.name === state.tableSelection)
            }
            return null
        }
    },
    actions: {
        async createDatabase ({ teamId, databaseName = '' }) {
            const database = await tablesApi.createDatabase(teamId, databaseName)
            this.databases[database.id] = database
            return database
        },
        async getDatabases () {
            const { team } = useAccountBridge()
            const databases = await tablesApi.getDataBases(team.id)
            databases.forEach(db => { this.databases[db.id] = db })
            return databases
        },
        async getTables (databaseId) {
            const { team } = useAccountBridge()
            let tables = await tablesApi.getTables(team.id, databaseId)
            tables = [...tables].sort((a, b) => a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: 'base' }))
            this.tables[databaseId] = tables
            if (tables.length > 0) this.tableSelection = tables[0].name
            return tables
        },
        clearState () { Object.assign(this, { databases: {}, databaseSelection: null, tables: {}, tableSelection: null, newTable: { name: '', columns: [{ ...emptyColumn }] }, isLoading: false }) },
        updateTableSelection (tableName) { this.tableSelection = tableName },
        updateDatabaseSelection (databaseId) { this.databaseSelection = databaseId },
        async getTableSchema ({ databaseId, tableName, teamId }) {
            const schema = await tablesApi.getTableSchema(teamId, databaseId, tableName)
            schema.forEach(col => { col.safeName = hashString(col.name) })
            Object.keys(this.tables[databaseId]).forEach(key => {
                if (this.tables[databaseId][key].name === tableName) {
                    this.tables[databaseId][key].schema = schema
                }
            })
        },
        async getTableData ({ databaseId, tableName, teamId }) {
            const data = await tablesApi.getTableData(teamId, databaseId, tableName)
            const payload = {
                data,
                safe: data.map(row => Object.fromEntries(Object.entries(row).map(([k, v]) => [hashString(k), v])))
            }
            Object.keys(this.tables[databaseId]).forEach(key => {
                if (this.tables[databaseId][key].name === tableName) {
                    this.tables[databaseId][key].payload = payload
                }
            })
        },
        async createTable ({ databaseId }) {
            const { team } = useAccountBridge()
            const sanitizedColumns = this.newTable.columns.map(col => {
                const c = { ...col }
                if (!c.hasDefault) delete c.default
                if (!c.unsigned) delete c.unsigned
                return c
            })
            return tablesApi.createTable(team.id, databaseId, { name: this.newTable.name, columns: sanitizedColumns })
        },
        deleteTable ({ teamId, databaseId, tableName }) {
            return tablesApi.deleteTable(teamId, databaseId, tableName)
        },
        addNewTableColumn () { this.newTable.columns.push({ ...emptyColumn }) },
        removeNewTableColumn (columnKey) { this.newTable.columns.splice(columnKey, 1) },
        setTableLoadingState (isLoading) { this.isLoading = isLoading }
    },
    persist: {
        pick: ['databases', 'newTable'],
        storage: localStorage
    }
})

6.2 — Bridge: clearState called from Vuex account

The Vuex account module's clearOtherStores action dispatches product/tables/clearState. Until account is migrated, add a Pinia call alongside the existing Vuex dispatch in store/modules/account/index.js:

// In Vuex account/clearOtherStores action (temporary)
async clearOtherStores ({ dispatch }) {
    // existing:
    dispatch('product/tables/clearState', null, { root: true })
    // add:
    const { useProductTablesStore } = await import('@/stores/product-tables.js')
    useProductTablesStore().clearState()
}

Remove the bridge once account is migrated.

6.3 — Find and update all consumers

grep -rl "product/tables\|mapState.*tables\|mapActions.*tables\|mapGetters.*tables" frontend/src/

Primary consumers: frontend/src/pages/team/Tables/ (20+ files).

For each consumer, check if it's inside a mixin or rendered as <component :is="..."> inside <ff-dialog> — use Pattern C from PINIA_COMPONENT_PATTERNS.md (mapState / mapActions from Pinia) — this works correctly in all component types.

6.4 — Delete the Vuex module

Remove tables from store/modules/product/index.js modules registration. Delete frontend/src/store/modules/product/tables/index.js.

Logout bridge: uncomment useProductTablesStore().$reset() in the Vuex logout action (Task 0.7).

6.5 — Write store tests

frfr

6.6 — Export from stores index

export { useProductTablesStore } from './product-tables.js'

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

Status

Done

Status

Closed / Done

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions