update seed blend

This commit is contained in:
Jake Kasper
2025-09-03 11:44:14 -04:00
parent 1826bf2702
commit af302719be
7 changed files with 85 additions and 46 deletions

View File

@@ -1,18 +1,11 @@
node_modules
build
dist
.cache
.git
.gitignore
README.md
.env
.nyc_output
coverage
.coverage
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.DS_Store
.vscode
.idea
*.swp
*.swo
*~
*.log
coverage
tmp
.env
*.local

View File

@@ -12,11 +12,6 @@ RUN npm install --only=production --silent
# Copy source code
COPY . .
# Use the pre-created non-root user from the Node image
# and ensure ownership is correct
RUN chown -R node:node /app
USER node
# Expose port
EXPOSE 5000

View File

@@ -389,6 +389,7 @@ router.get('/products', async (req, res, next) => {
categoryName: product.category_name,
productType: product.product_type,
activeIngredients: product.active_ingredients,
seedBlend: product.seed_blend,
description: product.description,
rateCount: parseInt(product.rate_count),
usageCount: parseInt(product.usage_count),

View File

@@ -1,18 +1,11 @@
node_modules
build
dist
.cache
.git
.gitignore
README.md
.env
.nyc_output
coverage
.coverage
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.DS_Store
.vscode
.idea
*.swp
*.swo
*~
*.log
coverage
tmp
.env
*.local

View File

@@ -3,20 +3,13 @@ FROM node:18-alpine
# Set working directory
WORKDIR /app
# Copy package files
# Copy package files and install dependencies
COPY package*.json ./
# Install dependencies
RUN npm install --silent
# Copy source code
COPY . .
# Use the pre-created non-root user from the Node image
# and ensure ownership is correct
RUN chown -R node:node /app
USER node
# Expose port
EXPOSE 3000

View File

@@ -45,6 +45,7 @@ const AdminProducts = () => {
productType: 'granular',
activeIngredients: '',
description: '',
seedBlend: [],
rates: [{ applicationType: 'granular', rateAmount: '', rateUnit: 'lbs/1000 sq ft', notes: '' }]
});
@@ -201,6 +202,7 @@ const AdminProducts = () => {
productType: 'granular',
activeIngredients: '',
description: '',
seedBlend: [],
rates: [{ applicationType: 'granular', rateAmount: '', rateUnit: 'lbs/1000 sq ft', notes: '' }]
});
setSelectedProduct(null);
@@ -215,6 +217,7 @@ const AdminProducts = () => {
productType: product.productType || product.customProductType || 'granular',
activeIngredients: product.activeIngredients || product.customActiveIngredients || '',
description: product.description || product.customDescription || '',
seedBlend: product.seedBlend || [],
rates: product.rates && product.rates.length > 0 ? product.rates : [{
applicationType: product.productType || 'granular',
rateAmount: product.customRateAmount || '',
@@ -340,6 +343,67 @@ const AdminProducts = () => {
/>
</div>
{/* Seed blend editor */}
{formData.productType === 'seed' && (
<div>
<div className="flex items-center justify-between mb-2">
<label className="block text-sm font-medium text-gray-700">Seed Blend (Cultivars + %)</label>
<button
type="button"
onClick={() => setFormData({ ...formData, seedBlend: [...(formData.seedBlend||[]), { cultivar: '', percent: '' }] })}
className="text-sm text-blue-600 hover:text-blue-800"
>
+ Add Cultivar
</button>
</div>
{(formData.seedBlend && formData.seedBlend.length > 0) ? (
<div className="space-y-2">
{formData.seedBlend.map((row, idx) => (
<div key={idx} className="grid grid-cols-6 gap-2 items-center">
<input
className="col-span-4 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Cultivar name"
value={row.cultivar}
onChange={(e)=> {
const next = [...formData.seedBlend];
next[idx] = { ...next[idx], cultivar: e.target.value };
setFormData({ ...formData, seedBlend: next });
}}
/>
<input
type="number"
step="0.1"
className="col-span-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="%"
value={row.percent}
onChange={(e)=> {
const next = [...formData.seedBlend];
next[idx] = { ...next[idx], percent: e.target.value };
setFormData({ ...formData, seedBlend: next });
}}
/>
<button
type="button"
className="text-red-600"
onClick={()=> {
const next = (formData.seedBlend||[]).filter((_,i)=> i!==idx);
setFormData({ ...formData, seedBlend: next });
}}
>
Remove
</button>
</div>
))}
<div className="text-xs text-gray-600">
Total: {((formData.seedBlend||[]).reduce((s,r)=> s + (parseFloat(r.percent)||0), 0)).toFixed(1)}%
</div>
</div>
) : (
<div className="text-sm text-gray-500">No cultivars added yet.</div>
)}
</div>
)}
{/* Application Rates */}
<div>
<div className="flex justify-between items-center mb-2">

View File

@@ -1471,7 +1471,7 @@ const EditProductModal = ({ product, onSubmit, onCancel, sharedProducts, categor
};
// Editor component for seed blends
const SeedBlendEditor = ({ value = [], onChange }) => {
function SeedBlendEditor({ value = [], onChange }) {
const [rows, setRows] = React.useState(value || []);
React.useEffect(()=>{ onChange && onChange(rows); }, [rows]);
const addRow = () => setRows([...(rows||[]), { cultivar: '', percent: '' }]);
@@ -1502,6 +1502,6 @@ const SeedBlendEditor = ({ value = [], onChange }) => {
)}
</div>
);
};
}
export default Products;