Implemented dependent dropdowns, updated AI prompt for strict hierarchy and clarification logic, and added security rules

This commit is contained in:
DJP 2025-11-19 22:21:20 -05:00
parent 0b363dbfa8
commit 7885343cbb
6 changed files with 652 additions and 53 deletions

19
.htaccess Normal file
View file

@ -0,0 +1,19 @@
# Protect sensitive files
<FilesMatch "^(config\.php|sheet_helpers\.php|.*\.json|.*\.log|^\..*)">
Order Allow,Deny
Deny from all
</FilesMatch>
# Prevent directory listing
Options -Indexes
# Allow access to specific public files if needed, but the above rule is specific enough.
# The above rule blocks:
# - config.php
# - sheet_helpers.php
# - Any .json file (data.json, sheets/*.json)
# - Any .log file (activity.log)
# - Any hidden file (.git, .DS_Store)
# Ensure index.php is the default index
DirectoryIndex index.php

50
api.php
View file

@ -191,29 +191,31 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
- Number (Auto-generated, do not invent)
- Title (String)
- Status (Enum: Booked, To-do, In Progress, Done) - Default to 'Booked'
- Category (Enum: 'Digital', 'DOOH', 'Online - Banner - Celtra', 'OOH', 'Print', 'Video')
- Media (Enum: 'POS', 'OOH', 'Social', 'Community management', 'Online advertising', 'Banner', 'Rich media', 'Landing page', 'Static Image', 'Video', 'Push notifications', '.com', 'Print', 'Digital', 'Direct mail', 'Packaging', 'TV', 'Cinema', 'Radio', 'VOD')
- Sub-media (Dependent on Media):
* POS: Wobbler, Display, Sticker, Poster, Shelf Talker
* OOH: Billboard, City Light, Bus Stop, Digital Screen, Transit
* Social: Instagram, Facebook, TikTok, LinkedIn, Twitter/X, Pinterest, Snapchat, YouTube
* Community management: Post, Reply, Story, Moderation
* Online advertising: Display, Native, Programmatic
* Banner: Standard, Animated, HTML5, Static
* Rich media: Expandable, Interstitial, Video
* Landing page: Microsite, Product Page, Campaign Page
* Static Image: JPEG, PNG, Hero Image
* Video: TVC, OLV, Social Video, Bumper, Long Form
* Push notifications: App Push, Web Push
* .com: Homepage, Product Page, Blog
* Print: Magazine, Newspaper, Flyer, Poster, Brochure
* Digital: Banner, Email, Website, App
* Direct mail: Letter, Postcard, Catalog
* Packaging: Box, Label, Bag
* TV: Spot, Sponsorship
* Cinema: Pre-roll, Spot
* Radio: Spot, Sponsorship
* VOD: Pre-roll, Mid-roll
- Category (Enum: 'Digital', 'Print', 'Out of Home', 'Video')
- Media (Dependent on Category - see below)
- Sub-media (Dependent on Category AND Media - see below)
HIERARCHY RULES (Strictly follow these combinations):
1. Category: Digital
- Media: Online Advertising -> Sub-media: Banner, Rich Media, Landing Page, Static Image, Video, Push notifications, .com
- Media: Social -> Sub-media: GIF, Video, Static Image, Multi-Asset Build
- Media: Community management -> Sub-media: (None)
- Media: POS -> Sub-media: Digital
2. Category: Print
- Media: POS -> Sub-media: Print
- Media: Out of Home -> Sub-media: Print
- Media: Direct Mail -> Sub-media: Print
- Media: Packaging -> Sub-media: Print
3. Category: Out of Home
- Media: Out of Home -> Sub-media: Print, Digital
4. Category: Video
- Media: POS -> Sub-media: Video
- Media: Online Advertising -> Sub-media: Video
- Media: Social -> Sub-media: Video
- Media: Broadcast -> Sub-media: TV, Cinema, Radio, VOD
- Format (String) - Extract sizes/dimensions here! e.g., '300x250', 'A4', '10x15cm', 'Full Page', '1080p'.
- Supply date (YYYY-MM-DD)
- Live date (YYYY-MM-DD)
@ -247,6 +249,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
Your calculation: 2 formats × 4 countries × 2 titles = 16 items
Since 16 10, return: {\"operation\": \"question\", \"text\": \"I calculated 16 items (2 formats × 4 countries × 2 titles). Did you mean 16 items total, or should I create only 10 items with a different pattern?\"}
* ONLY create items if the math matches OR if the user didn't specify a total count.
* **EXCEPTION - CONFIRMATION**: If the user's input is a confirmation of the count (e.g., \"Yes\", \"20\", \"Correct\"), DO NOT ask again. EXECUTE with the confirmed count.
* **CHECK HISTORY**: If you just asked \"I calculated X...\", and user says \"X\", PROCEED immediately.
- If the request is vague (no title, format, etc.), ask for clarification using the 'question' operation.

View file

@ -91,34 +91,34 @@ document.addEventListener('DOMContentLoaded', () => {
{ id: 'VI', name: 'Vietnamese' }
];
const categoryOptions = ['Digital', 'DOOH', 'Online - Banner - Celtra', 'OOH', 'Print', 'Video'];
const mediaOptions = ['POS', 'OOH', 'Social', 'Community management', 'Online advertising', 'Banner', 'Rich media', 'Landing page', 'Static Image', 'Video', 'Push notifications', '.com', 'Print', 'Digital', 'Direct mail', 'Packaging', 'TV', 'Cinema', 'Radio', 'VOD'];
const subMediaMap = {
'POS': ['Wobbler', 'Display', 'Sticker', 'Poster', 'Shelf Talker'],
'OOH': ['Billboard', 'City Light', 'Bus Stop', 'Digital Screen', 'Transit'],
'Social': ['Instagram', 'Facebook', 'TikTok', 'LinkedIn', 'Twitter/X', 'Pinterest', 'Snapchat', 'YouTube'],
'Community management': ['Post', 'Reply', 'Story', 'Moderation'],
'Online advertising': ['Display', 'Native', 'Programmatic'],
'Banner': ['Standard', 'Animated', 'HTML5', 'Static'],
'Rich media': ['Expandable', 'Interstitial', 'Video'],
'Landing page': ['Microsite', 'Product Page', 'Campaign Page'],
'Static Image': ['JPEG', 'PNG', 'Hero Image'],
'Video': ['TVC', 'OLV', 'Social Video', 'Bumper', 'Long Form'],
'Push notifications': ['App Push', 'Web Push'],
'.com': ['Homepage', 'Product Page', 'Blog'],
'Print': ['Magazine', 'Newspaper', 'Flyer', 'Poster', 'Brochure'],
'Digital': ['Banner', 'Email', 'Website', 'App'],
'Direct mail': ['Letter', 'Postcard', 'Catalog'],
'Packaging': ['Box', 'Label', 'Bag'],
'TV': ['Spot', 'Sponsorship'],
'Cinema': ['Pre-roll', 'Spot'],
'Radio': ['Spot', 'Sponsorship'],
'VOD': ['Pre-roll', 'Mid-roll']
// --- Data Hierarchy ---
const hierarchy = {
'Digital': {
'Online Advertising': ['Banner', 'Rich Media', 'Landing Page', 'Static Image', 'Video', 'Push notifications', '.com'],
'Social': ['GIF', 'Video', 'Static Image', 'Multi-Asset Build'],
'Community management': [], // No sub-media
'POS': ['Digital']
},
'Print': {
'POS': ['Print'],
'Out of Home': ['Print'],
'Direct Mail': ['Print'],
'Packaging': ['Print']
},
'Out of Home': {
'Out of Home': ['Print', 'Digital']
},
'Video': {
'POS': ['Video'],
'Online Advertising': ['Video'],
'Social': ['Video'],
'Broadcast': ['TV', 'Cinema', 'Radio', 'VOD'] // Assumed 'War Cost' -> Broadcast
}
};
// Flatten unique sub-media options for the source
const allSubMediaOptions = [...new Set(Object.values(subMediaMap).flat())].sort();
const categoryOptions = Object.keys(hierarchy);
const allMediaOptions = [...new Set(Object.values(hierarchy).flatMap(Object.keys))].sort();
const allSubMediaOptions = [...new Set(Object.values(hierarchy).flatMap(m => Object.values(m)).flat())].sort();
// --- Sheet Management ---
let activeSheetId = localStorage.getItem('activeSheetId') || null;
@ -355,7 +355,21 @@ document.addEventListener('DOMContentLoaded', () => {
{ type: 'text', title: 'Title', width: 300, name: 'Title' },
{ type: 'dropdown', title: 'Status', width: 100, name: 'Status', source: statusOptions },
{ type: 'dropdown', title: 'Category', width: 120, name: 'Category', source: categoryOptions },
{ type: 'dropdown', title: 'Media', width: 120, name: 'Media', source: mediaOptions },
{
type: 'dropdown',
title: 'Media',
width: 120,
name: 'Media',
source: allMediaOptions,
filter: function (instance, cell, c, r, source) {
const jexcel = instance.jexcel || instance;
const categoryValue = jexcel.getValueFromCoords(3, r); // Category is at index 3
if (hierarchy[categoryValue]) {
return Object.keys(hierarchy[categoryValue]);
}
return source;
}
},
{
type: 'dropdown',
title: 'Sub-media',
@ -364,11 +378,13 @@ document.addEventListener('DOMContentLoaded', () => {
source: allSubMediaOptions,
filter: function (instance, cell, c, r, source) {
const jexcel = instance.jexcel || instance;
const categoryValue = jexcel.getValueFromCoords(3, r); // Category is at index 3
const mediaValue = jexcel.getValueFromCoords(4, r); // Media is at index 4
if (subMediaMap[mediaValue]) {
return subMediaMap[mediaValue];
if (hierarchy[categoryValue] && hierarchy[categoryValue][mediaValue]) {
return hierarchy[categoryValue][mediaValue];
}
return source;
return []; // Return empty if invalid combination
}
},
{ type: 'text', title: 'Format', width: 120, name: 'Format' },

View file

@ -0,0 +1,262 @@
[
{
"Title": "Christmas campaign men",
"Status": "Booked",
"Category": "Print",
"Media": "Print",
"Format": "50x70cm",
"Supply date": "2025-12-01",
"Live date": "2025-12-25",
"Language": "FR",
"Country": "FR",
"Quantity": 1,
"Number": "DEL-001"
},
{
"Title": "Christmas campaign men",
"Status": "Booked",
"Category": "Print",
"Media": "Print",
"Format": "50x70cm",
"Supply date": "2025-12-01",
"Live date": "2025-12-25",
"Language": "GB",
"Country": "GB",
"Quantity": 1,
"Number": "DEL-002"
},
{
"Title": "Christmas campaign men",
"Status": "Booked",
"Category": "Print",
"Media": "Print",
"Format": "50x70cm",
"Supply date": "2025-12-01",
"Live date": "2025-12-25",
"Language": "DE",
"Country": "DE",
"Quantity": 1,
"Number": "DEL-003"
},
{
"Title": "Christmas campaign men",
"Status": "Booked",
"Category": "Print",
"Media": "Print",
"Format": "50x70cm",
"Supply date": "2025-12-01",
"Live date": "2025-12-25",
"Language": "ES",
"Country": "ES",
"Quantity": 1,
"Number": "DEL-004"
},
{
"Title": "Christmas 20 25 women",
"Status": "Booked",
"Category": "Print",
"Media": "Print",
"Format": "50x70cm",
"Supply date": "2025-12-01",
"Live date": "2025-12-25",
"Language": "FR",
"Country": "FR",
"Quantity": 1,
"Number": "DEL-005"
},
{
"Title": "Christmas 20 25 women",
"Status": "Booked",
"Category": "Print",
"Media": "Print",
"Format": "50x70cm",
"Supply date": "2025-12-01",
"Live date": "2025-12-25",
"Language": "GB",
"Country": "GB",
"Quantity": 1,
"Number": "DEL-006"
},
{
"Title": "Christmas 20 25 women",
"Status": "Booked",
"Category": "Print",
"Media": "Print",
"Format": "50x70cm",
"Supply date": "2025-12-01",
"Live date": "2025-12-25",
"Language": "DE",
"Country": "DE",
"Quantity": 1,
"Number": "DEL-007"
},
{
"Title": "Christmas 20 25 women",
"Status": "Booked",
"Category": "Print",
"Media": "Print",
"Format": "50x70cm",
"Supply date": "2025-12-01",
"Live date": "2025-12-25",
"Language": "ES",
"Country": "ES",
"Quantity": 1,
"Number": "DEL-008"
},
{
"Title": "Christmas campaign men",
"Status": "Booked",
"Category": "Print",
"Media": "Print",
"Format": "100x160cm",
"Supply date": "2025-12-01",
"Live date": "2025-12-25",
"Language": "FR",
"Country": "FR",
"Quantity": 1,
"Number": "DEL-009"
},
{
"Title": "Christmas campaign men",
"Status": "Booked",
"Category": "Print",
"Media": "Print",
"Format": "100x160cm",
"Supply date": "2025-12-01",
"Live date": "2025-12-25",
"Language": "GB",
"Country": "GB",
"Quantity": 1,
"Number": "DEL-010"
},
{
"Title": "Christmas campaign men",
"Status": "Booked",
"Category": "Print",
"Media": "Print",
"Format": "100x160cm",
"Supply date": "2025-12-01",
"Live date": "2025-12-25",
"Language": "DE",
"Country": "DE",
"Quantity": 1,
"Number": "DEL-011"
},
{
"Title": "Christmas campaign men",
"Status": "Booked",
"Category": "Print",
"Media": "Print",
"Format": "100x160cm",
"Supply date": "2025-12-01",
"Live date": "2025-12-25",
"Language": "ES",
"Country": "ES",
"Quantity": 1,
"Number": "DEL-012"
},
{
"Title": "Christmas 20 25 women",
"Status": "Booked",
"Category": "Print",
"Media": "Print",
"Format": "100x160cm",
"Supply date": "2025-12-01",
"Live date": "2025-12-25",
"Language": "FR",
"Country": "FR",
"Quantity": 1,
"Number": "DEL-013"
},
{
"Title": "Christmas 20 25 women",
"Status": "Booked",
"Category": "Print",
"Media": "Print",
"Format": "100x160cm",
"Supply date": "2025-12-01",
"Live date": "2025-12-25",
"Language": "GB",
"Country": "GB",
"Quantity": 1,
"Number": "DEL-014"
},
{
"Title": "Christmas 20 25 women",
"Status": "Booked",
"Category": "Print",
"Media": "Print",
"Format": "100x160cm",
"Supply date": "2025-12-01",
"Live date": "2025-12-25",
"Language": "DE",
"Country": "DE",
"Quantity": 1,
"Number": "DEL-015"
},
{
"Title": "Christmas 20 25 women",
"Status": "Booked",
"Category": "Print",
"Media": "Print",
"Format": "100x160cm",
"Supply date": "2025-12-01",
"Live date": "2025-12-25",
"Language": "ES",
"Country": "ES",
"Quantity": 1,
"Number": "DEL-016"
},
{
"Title": "Christmas campaign men",
"Status": "Booked",
"Category": "Print",
"Media": "Print",
"Format": "50x70cm",
"Supply date": "2025-12-01",
"Live date": "2025-12-25",
"Language": "FR",
"Country": "FR",
"Quantity": 1,
"Number": "DEL-017"
},
{
"Title": "Christmas campaign men",
"Status": "Booked",
"Category": "Print",
"Media": "Print",
"Format": "50x70cm",
"Supply date": "2025-12-01",
"Live date": "2025-12-25",
"Language": "GB",
"Country": "GB",
"Quantity": 1,
"Number": "DEL-018"
},
{
"Title": "Christmas campaign men",
"Status": "Booked",
"Category": "Print",
"Media": "Print",
"Format": "50x70cm",
"Supply date": "2025-12-01",
"Live date": "2025-12-25",
"Language": "DE",
"Country": "DE",
"Quantity": 1,
"Number": "DEL-019"
},
{
"Title": "Christmas campaign men",
"Status": "Booked",
"Category": "Print",
"Media": "Print",
"Format": "50x70cm",
"Supply date": "2025-12-01",
"Live date": "2025-12-25",
"Language": "ES",
"Country": "ES",
"Quantity": 1,
"Number": "DEL-020"
}
]

View file

@ -0,0 +1,282 @@
[
{
"Number": "DEL-001",
"Title": "Christmas campaign men",
"Status": "Booked",
"Category": "Print",
"Media": "POS",
"Sub-media": "Print",
"Format": "50x70cm",
"Supply date": "2025-12-01",
"Live date": "2020-12-25",
"Language": "FR",
"Country": "FR",
"Quantity": 1
},
{
"Number": "DEL-002",
"Title": "Christmas campaign men",
"Status": "Booked",
"Category": "Print",
"Media": "POS",
"Sub-media": "Print",
"Format": "50x70cm",
"Supply date": "2025-12-01",
"Live date": "2020-12-25",
"Language": "EN",
"Country": "GB",
"Quantity": 1
},
{
"Number": "DEL-003",
"Title": "Christmas campaign men",
"Status": "Booked",
"Category": "Print",
"Media": "POS",
"Sub-media": "Print",
"Format": "50x70cm",
"Supply date": "2025-12-01",
"Live date": "2020-12-25",
"Language": "DE",
"Country": "DE",
"Quantity": 1
},
{
"Number": "DEL-004",
"Title": "Christmas campaign men",
"Status": "Booked",
"Category": "Print",
"Media": "POS",
"Sub-media": "Print",
"Format": "50x70cm",
"Supply date": "2025-12-01",
"Live date": "2020-12-25",
"Language": "ES",
"Country": "ES",
"Quantity": 1
},
{
"Number": "DEL-005",
"Title": "Christmas campaign women",
"Status": "Booked",
"Category": "Print",
"Media": "POS",
"Sub-media": "Print",
"Format": "50x70cm",
"Supply date": "2025-12-01",
"Live date": "2020-12-25",
"Language": "FR",
"Country": "FR",
"Quantity": 1
},
{
"Number": "DEL-006",
"Title": "Christmas campaign women",
"Status": "Booked",
"Category": "Print",
"Media": "POS",
"Sub-media": "Print",
"Format": "50x70cm",
"Supply date": "2025-12-01",
"Live date": "2020-12-25",
"Language": "EN",
"Country": "GB",
"Quantity": 1
},
{
"Number": "DEL-007",
"Title": "Christmas campaign women",
"Status": "Booked",
"Category": "Print",
"Media": "POS",
"Sub-media": "Print",
"Format": "50x70cm",
"Supply date": "2025-12-01",
"Live date": "2020-12-25",
"Language": "DE",
"Country": "DE",
"Quantity": 1
},
{
"Number": "DEL-008",
"Title": "Christmas campaign women",
"Status": "Booked",
"Category": "Print",
"Media": "POS",
"Sub-media": "Print",
"Format": "50x70cm",
"Supply date": "2025-12-01",
"Live date": "2020-12-25",
"Language": "ES",
"Country": "ES",
"Quantity": 1
},
{
"Number": "DEL-009",
"Title": "Christmas campaign men",
"Status": "Booked",
"Category": "Print",
"Media": "POS",
"Sub-media": "Print",
"Format": "100x160cm",
"Supply date": "2025-12-01",
"Live date": "2020-12-25",
"Language": "FR",
"Country": "FR",
"Quantity": 1
},
{
"Number": "DEL-010",
"Title": "Christmas campaign men",
"Status": "Booked",
"Category": "Print",
"Media": "POS",
"Sub-media": "Print",
"Format": "100x160cm",
"Supply date": "2025-12-01",
"Live date": "2020-12-25",
"Language": "EN",
"Country": "GB",
"Quantity": 1
},
{
"Number": "DEL-011",
"Title": "Christmas campaign men",
"Status": "Booked",
"Category": "Print",
"Media": "POS",
"Sub-media": "Print",
"Format": "100x160cm",
"Supply date": "2025-12-01",
"Live date": "2020-12-25",
"Language": "DE",
"Country": "DE",
"Quantity": 1
},
{
"Number": "DEL-012",
"Title": "Christmas campaign men",
"Status": "Booked",
"Category": "Print",
"Media": "POS",
"Sub-media": "Print",
"Format": "100x160cm",
"Supply date": "2025-12-01",
"Live date": "2020-12-25",
"Language": "ES",
"Country": "ES",
"Quantity": 1
},
{
"Number": "DEL-013",
"Title": "Christmas campaign women",
"Status": "Booked",
"Category": "Print",
"Media": "POS",
"Sub-media": "Print",
"Format": "100x160cm",
"Supply date": "2025-12-01",
"Live date": "2020-12-25",
"Language": "FR",
"Country": "FR",
"Quantity": 1
},
{
"Number": "DEL-014",
"Title": "Christmas campaign women",
"Status": "Booked",
"Category": "Print",
"Media": "POS",
"Sub-media": "Print",
"Format": "100x160cm",
"Supply date": "2025-12-01",
"Live date": "2020-12-25",
"Language": "EN",
"Country": "GB",
"Quantity": 1
},
{
"Number": "DEL-015",
"Title": "Christmas campaign women",
"Status": "Booked",
"Category": "Print",
"Media": "POS",
"Sub-media": "Print",
"Format": "100x160cm",
"Supply date": "2025-12-01",
"Live date": "2020-12-25",
"Language": "DE",
"Country": "DE",
"Quantity": 1
},
{
"Number": "DEL-016",
"Title": "Christmas campaign women",
"Status": "Booked",
"Category": "Print",
"Media": "POS",
"Sub-media": "Print",
"Format": "100x160cm",
"Supply date": "2025-12-01",
"Live date": "2020-12-25",
"Language": "ES",
"Country": "ES",
"Quantity": 1
},
{
"Number": "DEL-017",
"Title": "Christmas campaign men",
"Status": "Booked",
"Category": "Print",
"Media": "POS",
"Sub-media": "Print",
"Format": "50x70cm",
"Supply date": "2025-12-01",
"Live date": "2020-12-25",
"Language": "FR",
"Country": "FR",
"Quantity": 1
},
{
"Number": "DEL-018",
"Title": "Christmas campaign men",
"Status": "Booked",
"Category": "Print",
"Media": "POS",
"Sub-media": "Print",
"Format": "50x70cm",
"Supply date": "2025-12-01",
"Live date": "2020-12-25",
"Language": "EN",
"Country": "GB",
"Quantity": 1
},
{
"Number": "DEL-019",
"Title": "Christmas campaign men",
"Status": "Booked",
"Category": "Print",
"Media": "POS",
"Sub-media": "Print",
"Format": "50x70cm",
"Supply date": "2025-12-01",
"Live date": "2020-12-25",
"Language": "DE",
"Country": "DE",
"Quantity": 1
},
{
"Number": "DEL-020",
"Title": "Christmas campaign men",
"Status": "Booked",
"Category": "Print",
"Media": "POS",
"Sub-media": "Print",
"Format": "50x70cm",
"Supply date": "2025-12-01",
"Live date": "2020-12-25",
"Language": "ES",
"Country": "ES",
"Quantity": 1
}
]

View file

@ -31,6 +31,22 @@
"modified": "2025-11-20T02:39:11+00:00",
"itemCount": 16,
"user": "daveporter@oliver.agency"
},
{
"id": "1763606650508",
"name": "6546546",
"created": "2025-11-20T02:44:10+00:00",
"modified": "2025-11-20T02:44:45+00:00",
"itemCount": 20,
"user": "daveporter@oliver.agency"
},
{
"id": "1763607466781",
"name": "231233",
"created": "2025-11-20T02:57:46+00:00",
"modified": "2025-11-20T03:00:12+00:00",
"itemCount": 20,
"user": "daveporter@oliver.agency"
}
]
}